@agent-native/core 0.114.8 → 0.114.10
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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +21 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +31 -3
- package/corpus/core/src/agent/engine/anthropic-engine.ts +20 -3
- package/corpus/core/src/agent/engine/builder-engine.ts +56 -9
- package/corpus/core/src/agent/engine/first-event-timeout.ts +64 -0
- package/corpus/core/src/client/AssistantChat.tsx +7 -3
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +1 -1
- package/corpus/core/src/integrations/computer-supervision-store.ts +4 -4
- package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
- package/corpus/core/src/integrations/remote-commands-store.ts +5 -5
- package/corpus/core/src/integrations/remote-devices-store.ts +3 -3
- package/corpus/core/src/integrations/remote-push-store.ts +3 -3
- package/corpus/core/src/observability/traces.ts +38 -1
- package/corpus/core/src/server/action-routes.ts +23 -1
- package/corpus/core/src/server/http-response-telemetry.ts +6 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/account-deep-dive.ts +41 -6
- package/corpus/templates/analytics/changelog/2026-07-20-account-deep-dives-finish-with-partial-data-when-hubspot-lo.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-20-dashboard-email-reports-now-include-an-image-when-a-single-p.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +11 -3
- package/corpus/templates/calendar/actions/create-event.ts +5 -0
- package/corpus/templates/calendar/app/components/ThemeToggle.tsx +4 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +0 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +309 -421
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +349 -0
- package/corpus/templates/calendar/app/i18n-data.ts +110 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +6 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +3 -0
- package/corpus/templates/calendar/app/root.tsx +4 -2
- package/corpus/templates/calendar/changelog/2026-07-20-events-can-be-updated-inline-with-date-time-timezone-and-rep.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-20-public-booking-pages-no-longer-hydrate-with-a-theme-mismatch.md +6 -0
- package/corpus/templates/calendar/shared/api.ts +1 -0
- package/corpus/templates/clips/AGENTS.md +7 -0
- package/corpus/templates/clips/actions/create-recording.ts +51 -10
- package/corpus/templates/clips/actions/get-recording-player-data.ts +34 -5
- package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
- package/corpus/templates/clips/actions/list-recordings.ts +15 -4
- package/corpus/templates/clips/actions/regenerate-summary.ts +32 -0
- package/corpus/templates/clips/actions/search-recordings.ts +28 -6
- package/corpus/templates/clips/actions/view-screen.ts +54 -47
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -5
- package/corpus/templates/clips/app/components/library/recording-card.tsx +29 -32
- package/corpus/templates/clips/app/components/meetings/quick-ask-sidebar.tsx +13 -0
- package/corpus/templates/clips/changelog/2026-07-20-agent-summaries-use-bounded-transcript-context.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-clip-summaries-now-finish-without-sending-a-long-transcript-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-clips-agents-only-discover-recordings-you-own-or-have-alread.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-hosted-recordings-no-longer-start-with-an-impossible-buffered-upload.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-meeting-recordings-now-offer-a-cleaner-transcript-view-with-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-shared-clips-can-be-shared-with-limited-access-and-cards-sho.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +12 -8
- package/corpus/templates/clips/desktop/src/overlays/pill-logo.tsx +2 -15
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +107 -340
- package/corpus/templates/clips/desktop/src/styles.css +27 -145
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +6 -7
- package/corpus/templates/clips/server/lib/agent-recording-access.ts +73 -0
- package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/plan/app/lib/plan-local-bridge.ts +18 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +10 -11
- package/corpus/templates/plan/changelog/2026-07-20-bridged-local-plans-now-load-comments-from-the-local-workspace.md +6 -0
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +27 -4
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +19 -4
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +40 -7
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/first-event-timeout.d.ts +25 -0
- package/dist/agent/engine/first-event-timeout.d.ts.map +1 -0
- package/dist/agent/engine/first-event-timeout.js +49 -0
- package/dist/agent/engine/first-event-timeout.js.map +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +7 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +1 -1
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/computer-supervision-store.js +4 -4
- package/dist/integrations/computer-supervision-store.js.map +1 -1
- package/dist/integrations/pending-tasks-store.js +1 -1
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/remote-commands-store.js +5 -5
- package/dist/integrations/remote-commands-store.js.map +1 -1
- package/dist/integrations/remote-devices-store.js +3 -3
- package/dist/integrations/remote-devices-store.js.map +1 -1
- package/dist/integrations/remote-push-store.js +3 -3
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +23 -1
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +23 -1
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/http-response-telemetry.d.ts +2 -0
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +5 -1
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/package.json +2 -2
- package/src/agent/engine/ai-sdk-engine.ts +31 -3
- package/src/agent/engine/anthropic-engine.ts +20 -3
- package/src/agent/engine/builder-engine.ts +56 -9
- package/src/agent/engine/first-event-timeout.ts +64 -0
- package/src/client/AssistantChat.tsx +7 -3
- package/src/client/resources/ResourcesPanel.tsx +1 -1
- package/src/integrations/computer-supervision-store.ts +4 -4
- package/src/integrations/pending-tasks-store.ts +1 -1
- package/src/integrations/remote-commands-store.ts +5 -5
- package/src/integrations/remote-devices-store.ts +3 -3
- package/src/integrations/remote-push-store.ts +3 -3
- package/src/observability/traces.ts +38 -1
- package/src/server/action-routes.ts +23 -1
- package/src/server/http-response-telemetry.ts +6 -1
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
|
@@ -14,6 +14,8 @@ import { writeAppState } from "@agent-native/core/application-state";
|
|
|
14
14
|
import { getActiveFileUploadProviderForRequest } from "@agent-native/core/file-upload";
|
|
15
15
|
import type { UploadMode } from "@shared/recording-core.js";
|
|
16
16
|
import { MAX_UPLOAD_BYTES } from "@shared/upload-limits.js";
|
|
17
|
+
import { eq } from "drizzle-orm";
|
|
18
|
+
import { createError } from "h3";
|
|
17
19
|
|
|
18
20
|
import { getDb, schema } from "../server/db/index.js";
|
|
19
21
|
import {
|
|
@@ -25,7 +27,10 @@ import {
|
|
|
25
27
|
} from "../server/lib/recordings.js";
|
|
26
28
|
import { setResumableSession } from "../server/lib/resumable-session.js";
|
|
27
29
|
import { shouldEnableStreamingUpload } from "../server/lib/streaming-upload-mode.js";
|
|
28
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
allowsSqlRecordingChunkScratch,
|
|
32
|
+
STORAGE_SETUP_REQUIRED_REASON,
|
|
33
|
+
} from "../server/lib/video-storage.js";
|
|
29
34
|
import { createRecordingSchema } from "./lib/create-recording-schema.js";
|
|
30
35
|
import { DEFAULT_RECORDING_TITLE } from "./lib/title-source.js";
|
|
31
36
|
|
|
@@ -86,18 +91,49 @@ export default defineAction({
|
|
|
86
91
|
console.log(`Created recording "${title}" (${id})`);
|
|
87
92
|
|
|
88
93
|
// Initialize a resumable upload session so chunks are streamed to the
|
|
89
|
-
// provider during recording (no post-stop assembly).
|
|
90
|
-
//
|
|
91
|
-
// init fails.
|
|
94
|
+
// provider during recording (no post-stop assembly). Hosted deployments
|
|
95
|
+
// have no SQL chunk fallback, so never return a buffered target there.
|
|
92
96
|
let uploadMode: UploadMode = "buffered";
|
|
93
97
|
const uploadProvider = await getActiveFileUploadProviderForRequest();
|
|
98
|
+
const bufferedFallbackAvailable = allowsSqlRecordingChunkScratch();
|
|
99
|
+
const streamingEnabled = shouldEnableStreamingUpload({
|
|
100
|
+
client: args.streamingUploadClient,
|
|
101
|
+
mimeType: args.mimeType,
|
|
102
|
+
bufferedFallbackAvailable,
|
|
103
|
+
});
|
|
104
|
+
const streamingRequired = !bufferedFallbackAvailable;
|
|
105
|
+
|
|
106
|
+
const failUploadSetup = async (reason: string): Promise<never> => {
|
|
107
|
+
const failedAt = new Date().toISOString();
|
|
108
|
+
await db
|
|
109
|
+
.update(schema.recordings)
|
|
110
|
+
.set({ status: "failed", failureReason: reason, updatedAt: failedAt })
|
|
111
|
+
.where(eq(schema.recordings.id, id));
|
|
112
|
+
await writeAppState(`recording-upload-${id}`, {
|
|
113
|
+
recordingId: id,
|
|
114
|
+
status: "failed",
|
|
115
|
+
progress: 0,
|
|
116
|
+
failureReason: reason,
|
|
117
|
+
storageSetupRequired: reason === STORAGE_SETUP_REQUIRED_REASON,
|
|
118
|
+
updatedAt: failedAt,
|
|
119
|
+
});
|
|
120
|
+
throw createError({
|
|
121
|
+
statusCode: 503,
|
|
122
|
+
statusMessage: reason,
|
|
123
|
+
data: { retryable: true },
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
streamingRequired &&
|
|
129
|
+
(!streamingEnabled || !uploadProvider?.resumable)
|
|
130
|
+
) {
|
|
131
|
+
await failUploadSetup(STORAGE_SETUP_REQUIRED_REASON);
|
|
132
|
+
}
|
|
133
|
+
|
|
94
134
|
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
client: args.streamingUploadClient,
|
|
98
|
-
mimeType: args.mimeType,
|
|
99
|
-
bufferedFallbackAvailable: allowsSqlRecordingChunkScratch(),
|
|
100
|
-
}) &&
|
|
135
|
+
streamingEnabled &&
|
|
136
|
+
(args.requestStreaming === true || streamingRequired) &&
|
|
101
137
|
uploadProvider?.resumable
|
|
102
138
|
) {
|
|
103
139
|
try {
|
|
@@ -129,6 +165,11 @@ export default defineAction({
|
|
|
129
165
|
`[create-recording] resumable session ready for ${id}: provider=${uploadProvider.id}`,
|
|
130
166
|
);
|
|
131
167
|
} catch (err) {
|
|
168
|
+
if (streamingRequired) {
|
|
169
|
+
await failUploadSetup(
|
|
170
|
+
"Video storage could not start a resumable upload session. Refresh and try again.",
|
|
171
|
+
);
|
|
172
|
+
}
|
|
132
173
|
console.warn(
|
|
133
174
|
`[create-recording] resumable session init failed, falling back to buffered:`,
|
|
134
175
|
err instanceof Error ? err.message : String(err),
|
|
@@ -29,6 +29,7 @@ import { and, asc, eq, or, sql } from "drizzle-orm";
|
|
|
29
29
|
import { z } from "zod";
|
|
30
30
|
|
|
31
31
|
import { getDb, schema } from "../server/db/index.js";
|
|
32
|
+
import { isAgentRecordingCaller } from "../server/lib/agent-recording-access.js";
|
|
32
33
|
import { isMediaVerificationPending } from "../server/lib/media-verification-state.js";
|
|
33
34
|
import { resolvePlayerVideoUrl } from "../server/lib/player-video-url.js";
|
|
34
35
|
import {
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
parseTranscriptSegments,
|
|
47
48
|
} from "../shared/transcript-segments.js";
|
|
48
49
|
import { resolveTranscriptPresentation } from "../shared/transcript-status.js";
|
|
50
|
+
import { boundTranscriptForAgent } from "./lib/transcript-preview.js";
|
|
49
51
|
|
|
50
52
|
function safeJsonObject(raw: string | null | undefined) {
|
|
51
53
|
if (!raw) return {};
|
|
@@ -91,7 +93,7 @@ function recordingDeepLink(recordingId: string): string {
|
|
|
91
93
|
|
|
92
94
|
export default defineAction({
|
|
93
95
|
description:
|
|
94
|
-
"Fetch everything the player page needs for a recording: metadata, transcript, comments, reactions, chapters, CTAs, and the caller's effective role.",
|
|
96
|
+
"Fetch everything the player page needs for a recording: metadata, transcript, comments, reactions, chapters, CTAs, and the caller's effective role. Agent calls receive a bounded transcript payload; browser player calls receive the full transcript.",
|
|
95
97
|
schema: z.object({
|
|
96
98
|
recordingId: z.string().describe("Recording ID"),
|
|
97
99
|
}),
|
|
@@ -106,7 +108,7 @@ export default defineAction({
|
|
|
106
108
|
}),
|
|
107
109
|
},
|
|
108
110
|
http: { method: "GET" },
|
|
109
|
-
run: async (args) => {
|
|
111
|
+
run: async (args, ctx) => {
|
|
110
112
|
const access = await resolveAccess("recording", args.recordingId);
|
|
111
113
|
if (!access) {
|
|
112
114
|
throw new ForbiddenError(`No access to recording ${args.recordingId}`);
|
|
@@ -120,7 +122,19 @@ export default defineAction({
|
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
let hasExplicitShare = access.role === "owner";
|
|
123
|
-
if (
|
|
125
|
+
if (
|
|
126
|
+
rec.visibility === "public" &&
|
|
127
|
+
access.role !== "owner" &&
|
|
128
|
+
!rec.password &&
|
|
129
|
+
isAgentRecordingCaller(ctx?.caller)
|
|
130
|
+
) {
|
|
131
|
+
hasExplicitShare = true;
|
|
132
|
+
}
|
|
133
|
+
if (
|
|
134
|
+
rec.visibility === "public" &&
|
|
135
|
+
access.role !== "owner" &&
|
|
136
|
+
!hasExplicitShare
|
|
137
|
+
) {
|
|
124
138
|
const userEmail = getRequestUserEmail()?.trim().toLowerCase();
|
|
125
139
|
const orgId = getRequestOrgId();
|
|
126
140
|
const principals = [];
|
|
@@ -289,6 +303,13 @@ export default defineAction({
|
|
|
289
303
|
!transcript.fullText?.trim() &&
|
|
290
304
|
transcriptSegments.length === 0;
|
|
291
305
|
const transcriptPresentation = resolveTranscriptPresentation(transcript);
|
|
306
|
+
const agentTranscript =
|
|
307
|
+
ctx?.caller === "tool" || ctx?.caller === "mcp" || ctx?.caller === "a2a"
|
|
308
|
+
? boundTranscriptForAgent({
|
|
309
|
+
fullText: transcript?.fullText,
|
|
310
|
+
segments: transcriptSegments,
|
|
311
|
+
})
|
|
312
|
+
: null;
|
|
292
313
|
|
|
293
314
|
// Normalize the dev-fallback videoUrl:
|
|
294
315
|
// 1. Rewrite legacy `/api/uploads/:id/blob` to `/api/video/:id` so old
|
|
@@ -362,11 +383,19 @@ export default defineAction({
|
|
|
362
383
|
? "failed"
|
|
363
384
|
: transcriptPresentation.status,
|
|
364
385
|
language: transcript.language,
|
|
365
|
-
fullText: transcript.fullText,
|
|
386
|
+
fullText: agentTranscript?.fullText ?? transcript.fullText,
|
|
387
|
+
...(agentTranscript
|
|
388
|
+
? {
|
|
389
|
+
fullTextLength: agentTranscript.fullTextLength,
|
|
390
|
+
segmentCount: agentTranscript.segmentCount,
|
|
391
|
+
previewTruncated: agentTranscript.previewTruncated,
|
|
392
|
+
note: agentTranscript.note,
|
|
393
|
+
}
|
|
394
|
+
: {}),
|
|
366
395
|
failureReason: transcriptReadyButEmpty
|
|
367
396
|
? "No speech was detected by transcription. Check microphone and speech permissions, then retry transcription."
|
|
368
397
|
: transcriptPresentation.failureReason,
|
|
369
|
-
segments: transcriptSegments,
|
|
398
|
+
segments: agentTranscript?.segments ?? transcriptSegments,
|
|
370
399
|
cleanup: cleanupState
|
|
371
400
|
? {
|
|
372
401
|
status:
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export const TRANSCRIPT_PREVIEW_CHARS = 2_000;
|
|
2
|
+
export const AGENT_TRANSCRIPT_MAX_CHARS = 12_000;
|
|
3
|
+
export const AGENT_TRANSCRIPT_SEGMENT_MAX_CHARS = 12_000;
|
|
2
4
|
|
|
3
5
|
export interface TranscriptPreview {
|
|
4
6
|
recordingId: string;
|
|
@@ -46,3 +48,49 @@ export function buildTranscriptPreview({
|
|
|
46
48
|
: "The complete transcript fits in this snapshot.",
|
|
47
49
|
};
|
|
48
50
|
}
|
|
51
|
+
|
|
52
|
+
export interface BoundedAgentTranscript<T> {
|
|
53
|
+
fullText: string | null;
|
|
54
|
+
segments: T[];
|
|
55
|
+
fullTextLength: number;
|
|
56
|
+
segmentCount: number;
|
|
57
|
+
previewTruncated: boolean;
|
|
58
|
+
note: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function boundTranscriptForAgent<T>({
|
|
62
|
+
fullText,
|
|
63
|
+
segments,
|
|
64
|
+
}: {
|
|
65
|
+
fullText: string | null | undefined;
|
|
66
|
+
segments: T[];
|
|
67
|
+
}): BoundedAgentTranscript<T> {
|
|
68
|
+
const text = fullText ?? "";
|
|
69
|
+
const boundedSegments: T[] = [];
|
|
70
|
+
let segmentChars = 0;
|
|
71
|
+
|
|
72
|
+
for (const segment of segments) {
|
|
73
|
+
const nextChars = JSON.stringify(segment)?.length ?? 0;
|
|
74
|
+
if (segmentChars + nextChars > AGENT_TRANSCRIPT_SEGMENT_MAX_CHARS) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
boundedSegments.push(segment);
|
|
78
|
+
segmentChars += nextChars;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const previewTruncated =
|
|
82
|
+
text.length > AGENT_TRANSCRIPT_MAX_CHARS ||
|
|
83
|
+
boundedSegments.length < segments.length;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
fullText:
|
|
87
|
+
fullText == null ? null : text.slice(0, AGENT_TRANSCRIPT_MAX_CHARS),
|
|
88
|
+
segments: boundedSegments,
|
|
89
|
+
fullTextLength: text.length,
|
|
90
|
+
segmentCount: segments.length,
|
|
91
|
+
previewTruncated,
|
|
92
|
+
note: previewTruncated
|
|
93
|
+
? `Agent transcript payload is bounded to ${AGENT_TRANSCRIPT_MAX_CHARS.toLocaleString()} text characters and ${AGENT_TRANSCRIPT_SEGMENT_MAX_CHARS.toLocaleString()} serialized segment characters. It may end mid-sentence; do not infer that transcription stopped early.`
|
|
94
|
+
: "The complete transcript fits in this agent payload.",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
3
|
-
import { accessFilter } from "@agent-native/core/sharing";
|
|
4
3
|
import {
|
|
5
4
|
and,
|
|
6
5
|
asc,
|
|
@@ -16,6 +15,10 @@ import {
|
|
|
16
15
|
import { z } from "zod";
|
|
17
16
|
|
|
18
17
|
import { getDb, schema } from "../server/db/index.js";
|
|
18
|
+
import {
|
|
19
|
+
agentRecordingAccessFilter,
|
|
20
|
+
isAgentRecordingCaller,
|
|
21
|
+
} from "../server/lib/agent-recording-access.js";
|
|
19
22
|
import { resolvePlayerVideoUrl } from "../server/lib/player-video-url.js";
|
|
20
23
|
import {
|
|
21
24
|
getActiveOrganizationId,
|
|
@@ -62,7 +65,7 @@ export function resolveListRecordingMedia(
|
|
|
62
65
|
|
|
63
66
|
export default defineAction({
|
|
64
67
|
description:
|
|
65
|
-
"List recordings visible to the current user. Supports filtering by view (library/shared/space/archive/trash/all), folder, space, tag, free-text, and sort.
|
|
68
|
+
"List recordings visible to the current user. Supports filtering by view (library/shared/space/archive/trash/all), folder, space, tag, free-text, and sort. Public/unlisted recordings are discoverable only when owned by or previously viewed by the current user; the shared view returns accessible recordings owned by someone else.",
|
|
66
69
|
schema: z.object({
|
|
67
70
|
view: z
|
|
68
71
|
.enum(["library", "shared", "space", "archive", "trash", "all"])
|
|
@@ -110,11 +113,19 @@ export default defineAction({
|
|
|
110
113
|
.describe("Include playable media fields for editor workflows"),
|
|
111
114
|
}),
|
|
112
115
|
http: { method: "GET" },
|
|
113
|
-
run: async (args) => {
|
|
116
|
+
run: async (args, ctx) => {
|
|
114
117
|
const db = getDb();
|
|
115
118
|
|
|
116
119
|
const whereClauses = [
|
|
117
|
-
|
|
120
|
+
agentRecordingAccessFilter(
|
|
121
|
+
schema.recordings,
|
|
122
|
+
schema.recordingShares,
|
|
123
|
+
schema.recordingViewers,
|
|
124
|
+
{
|
|
125
|
+
agentOnly: isAgentRecordingCaller(ctx?.caller),
|
|
126
|
+
userEmail: ctx?.userEmail,
|
|
127
|
+
},
|
|
128
|
+
),
|
|
118
129
|
];
|
|
119
130
|
|
|
120
131
|
const orgId = await getActiveOrganizationId();
|
|
@@ -15,6 +15,7 @@ import { z } from "zod";
|
|
|
15
15
|
|
|
16
16
|
import { getDb, schema } from "../server/db/index.js";
|
|
17
17
|
import { withFullVideoAiInstructions } from "../shared/clips-ai-prefs.js";
|
|
18
|
+
import cleanupTranscript from "./cleanup-transcript.js";
|
|
18
19
|
import { readIncludeFullVideoInAi } from "./lib/clips-ai-prefs.js";
|
|
19
20
|
|
|
20
21
|
export default defineAction({
|
|
@@ -65,6 +66,37 @@ export default defineAction({
|
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
if (!includeFullVideoInAi) {
|
|
70
|
+
const result = await cleanupTranscript.run({
|
|
71
|
+
transcript: transcript?.fullText ?? "",
|
|
72
|
+
task: "summary",
|
|
73
|
+
context: `Clip title: ${rec.title}`,
|
|
74
|
+
});
|
|
75
|
+
const description = result.summaryMd?.trim();
|
|
76
|
+
if (!description) {
|
|
77
|
+
return {
|
|
78
|
+
updated: false,
|
|
79
|
+
skipped: true,
|
|
80
|
+
reason: "summary_empty",
|
|
81
|
+
recordingId: args.recordingId,
|
|
82
|
+
provider: result.provider,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await db
|
|
87
|
+
.update(schema.recordings)
|
|
88
|
+
.set({ description, updatedAt: new Date().toISOString() })
|
|
89
|
+
.where(eq(schema.recordings.id, args.recordingId));
|
|
90
|
+
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
updated: true,
|
|
94
|
+
recordingId: args.recordingId,
|
|
95
|
+
description,
|
|
96
|
+
provider: result.provider,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
68
100
|
const baseMessage =
|
|
69
101
|
`Regenerate the description for recording ${args.recordingId}. ` +
|
|
70
102
|
`Read the transcript in this request's context and call ` +
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { defineAction, embedApp } from "@agent-native/core";
|
|
2
2
|
import { buildDeepLink } from "@agent-native/core/server";
|
|
3
|
-
import { accessFilter } from "@agent-native/core/sharing";
|
|
4
3
|
import { and, eq, sql } from "drizzle-orm";
|
|
5
4
|
import { z } from "zod";
|
|
6
5
|
|
|
7
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import {
|
|
8
|
+
agentRecordingAccessFilter,
|
|
9
|
+
isAgentRecordingCaller,
|
|
10
|
+
} from "../server/lib/agent-recording-access.js";
|
|
8
11
|
import { buildCaseInsensitiveSearchPattern } from "./search-recordings-utils.js";
|
|
9
12
|
|
|
10
13
|
const SNIPPET_RADIUS = 80;
|
|
@@ -92,7 +95,7 @@ function transcriptMatch(
|
|
|
92
95
|
|
|
93
96
|
export default defineAction({
|
|
94
97
|
description:
|
|
95
|
-
"Search recordings by title, description, transcript text, or comments. Transcript and comment matches include timestamps for jumping to the matching moment.",
|
|
98
|
+
"Search recordings by title, description, transcript text, or comments. Transcript and comment matches include timestamps for jumping to the matching moment. Public/unlisted recordings are searchable only when owned by or previously viewed by the current user.",
|
|
96
99
|
schema: z.object({
|
|
97
100
|
query: z.string().min(1).describe("Search text"),
|
|
98
101
|
limit: z.coerce.number().int().min(1).max(100).default(30),
|
|
@@ -108,9 +111,13 @@ export default defineAction({
|
|
|
108
111
|
}),
|
|
109
112
|
},
|
|
110
113
|
http: { method: "GET" },
|
|
111
|
-
run: async (args) => {
|
|
114
|
+
run: async (args, ctx) => {
|
|
112
115
|
const db = getDb();
|
|
113
116
|
const pattern = buildCaseInsensitiveSearchPattern(args.query);
|
|
117
|
+
const recordingAccess = {
|
|
118
|
+
agentOnly: isAgentRecordingCaller(ctx?.caller),
|
|
119
|
+
userEmail: ctx?.userEmail,
|
|
120
|
+
};
|
|
114
121
|
|
|
115
122
|
// Title/description matches on the recordings table
|
|
116
123
|
const recMatches = await db
|
|
@@ -128,7 +135,12 @@ export default defineAction({
|
|
|
128
135
|
.from(schema.recordings)
|
|
129
136
|
.where(
|
|
130
137
|
and(
|
|
131
|
-
|
|
138
|
+
agentRecordingAccessFilter(
|
|
139
|
+
schema.recordings,
|
|
140
|
+
schema.recordingShares,
|
|
141
|
+
schema.recordingViewers,
|
|
142
|
+
recordingAccess,
|
|
143
|
+
),
|
|
132
144
|
sql`(lower(${schema.recordings.title}) LIKE ${pattern} ESCAPE '\\' OR lower(${schema.recordings.description}) LIKE ${pattern} ESCAPE '\\')`,
|
|
133
145
|
),
|
|
134
146
|
)
|
|
@@ -158,7 +170,12 @@ export default defineAction({
|
|
|
158
170
|
)
|
|
159
171
|
.where(
|
|
160
172
|
and(
|
|
161
|
-
|
|
173
|
+
agentRecordingAccessFilter(
|
|
174
|
+
schema.recordings,
|
|
175
|
+
schema.recordingShares,
|
|
176
|
+
schema.recordingViewers,
|
|
177
|
+
recordingAccess,
|
|
178
|
+
),
|
|
162
179
|
sql`lower(${schema.recordingTranscripts.fullText}) LIKE ${pattern} ESCAPE '\\'`,
|
|
163
180
|
),
|
|
164
181
|
)
|
|
@@ -186,7 +203,12 @@ export default defineAction({
|
|
|
186
203
|
)
|
|
187
204
|
.where(
|
|
188
205
|
and(
|
|
189
|
-
|
|
206
|
+
agentRecordingAccessFilter(
|
|
207
|
+
schema.recordings,
|
|
208
|
+
schema.recordingShares,
|
|
209
|
+
schema.recordingViewers,
|
|
210
|
+
recordingAccess,
|
|
211
|
+
),
|
|
190
212
|
sql`lower(${schema.recordingComments.content}) LIKE ${pattern} ESCAPE '\\'`,
|
|
191
213
|
),
|
|
192
214
|
)
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
import { z } from "zod";
|
|
33
33
|
|
|
34
34
|
import { getDb, schema } from "../server/db/index.js";
|
|
35
|
+
import { agentRecordingAccessFilter } from "../server/lib/agent-recording-access.js";
|
|
35
36
|
import {
|
|
36
37
|
getActiveOrganizationId,
|
|
37
38
|
ownerEmailMatches,
|
|
@@ -87,7 +88,12 @@ async function fetchRecording(id: string) {
|
|
|
87
88
|
.where(
|
|
88
89
|
and(
|
|
89
90
|
eq(schema.recordings.id, id),
|
|
90
|
-
|
|
91
|
+
agentRecordingAccessFilter(
|
|
92
|
+
schema.recordings,
|
|
93
|
+
schema.recordingShares,
|
|
94
|
+
schema.recordingViewers,
|
|
95
|
+
{ agentOnly: true },
|
|
96
|
+
),
|
|
91
97
|
),
|
|
92
98
|
);
|
|
93
99
|
return row ? mapRecording(row) : null;
|
|
@@ -183,7 +189,12 @@ async function fetchLibrary({
|
|
|
183
189
|
const ownerEmail = getRequestUserEmail();
|
|
184
190
|
if (!ownerEmail) return [];
|
|
185
191
|
const conditions = [
|
|
186
|
-
|
|
192
|
+
agentRecordingAccessFilter(
|
|
193
|
+
schema.recordings,
|
|
194
|
+
schema.recordingShares,
|
|
195
|
+
schema.recordingViewers,
|
|
196
|
+
{ agentOnly: true },
|
|
197
|
+
),
|
|
187
198
|
isNull(schema.recordings.archivedAt),
|
|
188
199
|
isNull(schema.recordings.trashedAt),
|
|
189
200
|
];
|
|
@@ -480,13 +491,47 @@ async function fetchDictationDetail(dictationId: string) {
|
|
|
480
491
|
};
|
|
481
492
|
}
|
|
482
493
|
|
|
494
|
+
async function addRecordingContext(
|
|
495
|
+
screen: Record<string, unknown>,
|
|
496
|
+
recordingId: string,
|
|
497
|
+
) {
|
|
498
|
+
const recording = await fetchRecording(recordingId);
|
|
499
|
+
if (!recording) return;
|
|
500
|
+
|
|
501
|
+
const [transcript, comments, browserDiagnosticsSummary, bugReportSummary] =
|
|
502
|
+
await Promise.all([
|
|
503
|
+
fetchTranscript(recordingId),
|
|
504
|
+
fetchComments(recordingId),
|
|
505
|
+
fetchBrowserDiagnosticsSummary(recordingId),
|
|
506
|
+
fetchBugReportSummary(recordingId),
|
|
507
|
+
]);
|
|
508
|
+
screen.recording = recording;
|
|
509
|
+
if (transcript) {
|
|
510
|
+
screen.transcript = buildTranscriptPreview({
|
|
511
|
+
recordingId: transcript.recordingId,
|
|
512
|
+
language: transcript.language,
|
|
513
|
+
status: transcript.status,
|
|
514
|
+
fullText: transcript.fullText,
|
|
515
|
+
segments: transcript.segments,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
screen.comments = comments.slice(0, 50);
|
|
519
|
+
if (browserDiagnosticsSummary) {
|
|
520
|
+
screen.browserDiagnostics = {
|
|
521
|
+
summary: browserDiagnosticsSummary,
|
|
522
|
+
note: "Summary only. Call get-recording-player-data for full redacted diagnostics when you have editor access.",
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (bugReportSummary) screen.bugReport = bugReportSummary;
|
|
526
|
+
}
|
|
527
|
+
|
|
483
528
|
async function fetchShare(shareId: string) {
|
|
484
529
|
const db = getDb();
|
|
485
|
-
const [
|
|
530
|
+
const [share] = await db
|
|
486
531
|
.select()
|
|
487
532
|
.from(schema.recordingShares)
|
|
488
533
|
.where(eq(schema.recordingShares.id, shareId));
|
|
489
|
-
return
|
|
534
|
+
return share ?? null;
|
|
490
535
|
}
|
|
491
536
|
|
|
492
537
|
export default defineAction({
|
|
@@ -520,48 +565,7 @@ export default defineAction({
|
|
|
520
565
|
switch (nav.view) {
|
|
521
566
|
case "recording":
|
|
522
567
|
case "insights": {
|
|
523
|
-
if (nav.recordingId)
|
|
524
|
-
const recording = await fetchRecording(nav.recordingId);
|
|
525
|
-
if (recording) {
|
|
526
|
-
const [
|
|
527
|
-
transcript,
|
|
528
|
-
comments,
|
|
529
|
-
browserDiagnosticsSummary,
|
|
530
|
-
bugReportSummary,
|
|
531
|
-
] = await Promise.all([
|
|
532
|
-
fetchTranscript(nav.recordingId),
|
|
533
|
-
fetchComments(nav.recordingId),
|
|
534
|
-
fetchBrowserDiagnosticsSummary(nav.recordingId),
|
|
535
|
-
fetchBugReportSummary(nav.recordingId),
|
|
536
|
-
]);
|
|
537
|
-
screen.recording = recording;
|
|
538
|
-
if (transcript) {
|
|
539
|
-
// Ambient snapshot only — embed a short fullText snippet and the
|
|
540
|
-
// segment count, NOT the entire transcript + every word-level
|
|
541
|
-
// segment. The full transcript can be tens of thousands of tokens
|
|
542
|
-
// and is injected into <current-screen> on EVERY message, which
|
|
543
|
-
// can blow the model's context window. The agent calls
|
|
544
|
-
// get-recording-player-data for the complete transcript/segments.
|
|
545
|
-
screen.transcript = buildTranscriptPreview({
|
|
546
|
-
recordingId: transcript.recordingId,
|
|
547
|
-
language: transcript.language,
|
|
548
|
-
status: transcript.status,
|
|
549
|
-
fullText: transcript.fullText,
|
|
550
|
-
segments: transcript.segments,
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
screen.comments = comments.slice(0, 50);
|
|
554
|
-
if (browserDiagnosticsSummary) {
|
|
555
|
-
screen.browserDiagnostics = {
|
|
556
|
-
summary: browserDiagnosticsSummary,
|
|
557
|
-
note: "Summary only. Call get-recording-player-data for full redacted diagnostics when you have editor access.",
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
if (bugReportSummary) {
|
|
561
|
-
screen.bugReport = bugReportSummary;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
568
|
+
if (nav.recordingId) await addRecordingContext(screen, nav.recordingId);
|
|
565
569
|
break;
|
|
566
570
|
}
|
|
567
571
|
case "library": {
|
|
@@ -611,7 +615,10 @@ export default defineAction({
|
|
|
611
615
|
case "embed": {
|
|
612
616
|
if (nav.shareId) {
|
|
613
617
|
const share = await fetchShare(nav.shareId);
|
|
614
|
-
if (share)
|
|
618
|
+
if (share) {
|
|
619
|
+
screen.share = share;
|
|
620
|
+
await addRecordingContext(screen, share.resourceId);
|
|
621
|
+
}
|
|
615
622
|
}
|
|
616
623
|
break;
|
|
617
624
|
}
|
|
@@ -373,11 +373,7 @@ export function LibraryGrid({
|
|
|
373
373
|
onToggleSelect={
|
|
374
374
|
canManageRecordings ? toggleSelect : undefined
|
|
375
375
|
}
|
|
376
|
-
onShare={
|
|
377
|
-
canManageRecordings
|
|
378
|
-
? (rec) => setSharingRec(rec)
|
|
379
|
-
: undefined
|
|
380
|
-
}
|
|
376
|
+
onShare={(rec) => setSharingRec(rec)}
|
|
381
377
|
moveTargets={moveTargets}
|
|
382
378
|
onMove={canMoveSelection ? moveSingle : undefined}
|
|
383
379
|
isMovePending={moveRecording.isPending}
|
|
@@ -125,8 +125,7 @@ export function RecordingCard({
|
|
|
125
125
|
);
|
|
126
126
|
const canMove = Boolean(onMove && moveTargets.length > 0);
|
|
127
127
|
const canSelect = Boolean(onToggleSelect) && !readOnly;
|
|
128
|
-
const showActions =
|
|
129
|
-
!readOnly && Boolean(onShare || onMove || onArchive || onTrash);
|
|
128
|
+
const showActions = Boolean(onShare || onMove || onArchive || onTrash);
|
|
130
129
|
const hasDefaultTitle = isDefaultTitle(recording.title);
|
|
131
130
|
const displayTitle = hasDefaultTitle
|
|
132
131
|
? t("editableTitle.untitled")
|
|
@@ -330,6 +329,17 @@ export function RecordingCard({
|
|
|
330
329
|
{displayTitle}
|
|
331
330
|
</div>
|
|
332
331
|
)}
|
|
332
|
+
<div className="mt-1 flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground">
|
|
333
|
+
<Avatar className="h-4 w-4 shrink-0">
|
|
334
|
+
<AvatarImage src="" alt={recording.ownerEmail} />
|
|
335
|
+
<AvatarFallback className="bg-primary/15 text-[8px] text-primary">
|
|
336
|
+
{ownerInitials}
|
|
337
|
+
</AvatarFallback>
|
|
338
|
+
</Avatar>
|
|
339
|
+
<span className="min-w-0 truncate">{recording.ownerEmail}</span>
|
|
340
|
+
<span aria-hidden>•</span>
|
|
341
|
+
<span className="shrink-0">{relative}</span>
|
|
342
|
+
</div>
|
|
333
343
|
<div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-muted-foreground">
|
|
334
344
|
<PrivacyIcon
|
|
335
345
|
visibility={recording.visibility}
|
|
@@ -353,8 +363,6 @@ export function RecordingCard({
|
|
|
353
363
|
})}
|
|
354
364
|
</span>
|
|
355
365
|
)}
|
|
356
|
-
<span>•</span>
|
|
357
|
-
<span>{relative}</span>
|
|
358
366
|
</div>
|
|
359
367
|
</div>
|
|
360
368
|
|
|
@@ -446,34 +454,23 @@ export function RecordingCard({
|
|
|
446
454
|
)}
|
|
447
455
|
</div>
|
|
448
456
|
|
|
449
|
-
|
|
450
|
-
<
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
{t}
|
|
467
|
-
</span>
|
|
468
|
-
))}
|
|
469
|
-
{recording.tags.length > 2 && (
|
|
470
|
-
<span className="text-[10px] text-muted-foreground">
|
|
471
|
-
+{recording.tags.length - 2}
|
|
472
|
-
</span>
|
|
473
|
-
)}
|
|
474
|
-
</div>
|
|
475
|
-
)}
|
|
476
|
-
</div>
|
|
457
|
+
{recording.tags.length > 0 && (
|
|
458
|
+
<div className="flex items-center gap-1 truncate">
|
|
459
|
+
{recording.tags.slice(0, 2).map((t) => (
|
|
460
|
+
<span
|
|
461
|
+
key={t}
|
|
462
|
+
className="rounded-full bg-primary/10 text-primary text-[10px] px-1.5 py-0.5"
|
|
463
|
+
>
|
|
464
|
+
{t}
|
|
465
|
+
</span>
|
|
466
|
+
))}
|
|
467
|
+
{recording.tags.length > 2 && (
|
|
468
|
+
<span className="text-[10px] text-muted-foreground">
|
|
469
|
+
+{recording.tags.length - 2}
|
|
470
|
+
</span>
|
|
471
|
+
)}
|
|
472
|
+
</div>
|
|
473
|
+
)}
|
|
477
474
|
</div>
|
|
478
475
|
</div>
|
|
479
476
|
);
|
|
@@ -94,6 +94,19 @@ export function QuickAskSidebar({
|
|
|
94
94
|
return () => window.removeEventListener("keydown", onKeyDown);
|
|
95
95
|
}, []);
|
|
96
96
|
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const params = new URLSearchParams(window.location.search);
|
|
99
|
+
if (params.get("chat") !== "1") return;
|
|
100
|
+
setOpen(true);
|
|
101
|
+
params.delete("chat");
|
|
102
|
+
const nextQuery = params.toString();
|
|
103
|
+
window.history.replaceState(
|
|
104
|
+
window.history.state,
|
|
105
|
+
"",
|
|
106
|
+
`${window.location.pathname}${nextQuery ? `?${nextQuery}` : ""}${window.location.hash}`,
|
|
107
|
+
);
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
97
110
|
// Focus the composer whenever the sheet opens.
|
|
98
111
|
useEffect(() => {
|
|
99
112
|
if (open) {
|