@agent-native/core 0.114.4 → 0.114.5
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 +1 -1
- package/corpus/core/CHANGELOG.md +8 -0
- package/corpus/core/package.json +1 -1
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +11 -1
- package/corpus/templates/clips/AGENTS.md +14 -4
- package/corpus/templates/clips/actions/update-meeting.ts +13 -3
- package/corpus/templates/clips/actions/view-screen.ts +1 -0
- package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +2 -1
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +96 -4
- package/corpus/templates/clips/app/i18n/en-US.ts +11 -0
- package/corpus/templates/clips/app/lib/public-meeting.ts +85 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +11 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -7
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +241 -135
- package/corpus/templates/clips/changelog/2026-07-20-meeting-share-links-can-include-the-full-transcript-with-an-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-recording-retries-the-default-mac-microphone-when-a-saved-in.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-shared-clips-now-tell-agents-to-wait-while-uploads-and-trans.md +6 -0
- package/corpus/templates/clips/desktop/src/lib/meeting-join-url.ts +1 -33
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +65 -2
- package/corpus/templates/clips/server/db/schema.ts +3 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +31 -21
- package/corpus/templates/clips/server/plugins/auth.ts +1 -0
- package/corpus/templates/clips/server/plugins/db.ts +6 -0
- package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +13 -3
- package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +152 -0
- package/corpus/templates/clips/shared/agent-context.ts +60 -0
- package/corpus/templates/clips/shared/meeting-join-url.ts +36 -0
- 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/notifications/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/package.json +2 -2
|
@@ -224,6 +224,35 @@ function browserLocale(): string {
|
|
|
224
224
|
return navigator.language || "en-US";
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
function isUnavailableSelectedMicrophoneError(error: unknown): boolean {
|
|
228
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
229
|
+
return /selected microphone .+ is not available/i.test(message);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function transcriptionStartError(
|
|
233
|
+
error: unknown,
|
|
234
|
+
selectedMicrophoneUnavailable: boolean,
|
|
235
|
+
): Error {
|
|
236
|
+
if (selectedMicrophoneUnavailable) {
|
|
237
|
+
return new Error(
|
|
238
|
+
"Your selected microphone is no longer available. Clips tried your Mac's default microphone, but notes still could not start. Choose an available microphone in Clips settings, then try again.",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
243
|
+
if (
|
|
244
|
+
/screencapturekit|voiceprocessingi|microphone|audio capture|local .*capture/i.test(
|
|
245
|
+
message,
|
|
246
|
+
)
|
|
247
|
+
) {
|
|
248
|
+
return new Error(
|
|
249
|
+
"Clips could not start local audio capture. Check that Clips has Microphone and Screen Recording access in System Settings, then try again.",
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return new Error("Clips could not start local transcription. Try again.");
|
|
254
|
+
}
|
|
255
|
+
|
|
227
256
|
export function recordingTranscriptionLanguage(): string | null {
|
|
228
257
|
return null;
|
|
229
258
|
}
|
|
@@ -298,12 +327,46 @@ export async function startTranscriptionEngine(opts: {
|
|
|
298
327
|
);
|
|
299
328
|
return "whisper";
|
|
300
329
|
} catch (err) {
|
|
330
|
+
let fallbackMic = opts.mic;
|
|
331
|
+
const selectedMicrophoneUnavailable =
|
|
332
|
+
Boolean(opts.mic) && isUnavailableSelectedMicrophoneError(err);
|
|
301
333
|
console.warn(
|
|
302
334
|
"[transcription] whisper mic+system failed, falling back to mic-only:",
|
|
303
335
|
err,
|
|
304
336
|
);
|
|
305
|
-
|
|
306
|
-
|
|
337
|
+
if (selectedMicrophoneUnavailable) {
|
|
338
|
+
console.warn(
|
|
339
|
+
"[transcription] selected microphone is unavailable; retrying with the macOS default input:",
|
|
340
|
+
err,
|
|
341
|
+
);
|
|
342
|
+
try {
|
|
343
|
+
await restartTranscriptionEngine(
|
|
344
|
+
"whisper",
|
|
345
|
+
undefined,
|
|
346
|
+
captureSystem,
|
|
347
|
+
voiceProcessing,
|
|
348
|
+
emitPartials,
|
|
349
|
+
);
|
|
350
|
+
return "whisper";
|
|
351
|
+
} catch (defaultMicErr) {
|
|
352
|
+
console.warn(
|
|
353
|
+
"[transcription] default mic+system capture failed, falling back to default mic-only:",
|
|
354
|
+
defaultMicErr,
|
|
355
|
+
);
|
|
356
|
+
fallbackMic = undefined;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
await restartTranscriptionEngine("macos-native", fallbackMic);
|
|
361
|
+
return "macos-native";
|
|
362
|
+
} catch (fallbackErr) {
|
|
363
|
+
throw transcriptionStartError(
|
|
364
|
+
fallbackErr,
|
|
365
|
+
selectedMicrophoneUnavailable ||
|
|
366
|
+
(Boolean(opts.mic) &&
|
|
367
|
+
isUnavailableSelectedMicrophoneError(fallbackErr)),
|
|
368
|
+
);
|
|
369
|
+
}
|
|
307
370
|
}
|
|
308
371
|
}
|
|
309
372
|
|
|
@@ -425,6 +425,9 @@ export const meetings = table("clips_meetings", {
|
|
|
425
425
|
})
|
|
426
426
|
.notNull()
|
|
427
427
|
.default("idle"),
|
|
428
|
+
shareTranscript: integer("share_transcript", { mode: "boolean" })
|
|
429
|
+
.notNull()
|
|
430
|
+
.default(false),
|
|
428
431
|
summaryMd: text("summary_md").notNull().default(""),
|
|
429
432
|
// JSON array of `{ text }` bullets.
|
|
430
433
|
bulletsJson: text("bullets_json").notNull().default("[]"),
|
|
@@ -11,6 +11,7 @@ import { getRequestURL, setResponseHeader, type H3Event } from "h3";
|
|
|
11
11
|
import {
|
|
12
12
|
buildAgentApiUrls,
|
|
13
13
|
buildRecommendedFrames,
|
|
14
|
+
getAgentClipReadiness,
|
|
14
15
|
CLIP_AGENT_ACCESS_TOKEN_PREFIX,
|
|
15
16
|
CLIPS_AGENT_ACCESS_PARAM,
|
|
16
17
|
CLIP_AGENT_CONTEXT_VERSION,
|
|
@@ -538,18 +539,24 @@ export function buildPublicAgentContext({
|
|
|
538
539
|
const publicPageUrl = `${requestUrl.origin}${getServerAppBasePath()}/share/${encodeURIComponent(recording.id)}`;
|
|
539
540
|
const isLoomSource = isLoomRecordingSource(recording);
|
|
540
541
|
const isLoomEmbedBacked = isLoomEmbedBackedRecording(recording);
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
542
|
+
const agentReadiness = getAgentClipReadiness(recording.status);
|
|
543
|
+
const clipIsReady = agentReadiness.state === "ready";
|
|
544
|
+
const suggestedFrames =
|
|
545
|
+
!clipIsReady || isLoomEmbedBacked
|
|
546
|
+
? []
|
|
547
|
+
: buildRecommendedFrames({
|
|
548
|
+
durationMs: recording.durationMs,
|
|
549
|
+
chapters,
|
|
550
|
+
segments: agentSegments,
|
|
551
|
+
}).map((frame) => ({
|
|
552
|
+
...frame,
|
|
553
|
+
url: api.frameUrl(frame.atMs),
|
|
554
|
+
}));
|
|
551
555
|
const instructions = [
|
|
552
|
-
|
|
556
|
+
...(agentReadiness.instruction ? [agentReadiness.instruction] : []),
|
|
557
|
+
...(clipIsReady
|
|
558
|
+
? ["Use transcript.segments for timestamped spoken context."]
|
|
559
|
+
: []),
|
|
553
560
|
...transcriptStatusInstructions(transcript),
|
|
554
561
|
...(bugReport
|
|
555
562
|
? [
|
|
@@ -561,15 +568,17 @@ export function buildPublicAgentContext({
|
|
|
561
568
|
"Use browserDiagnostics.consoleLogs for the redacted console stream (all levels: debug/log/info/warn/error) and browserDiagnostics.networkRequests for the fetch/XHR requests (method, sanitized URL, status, duration) captured during the recording. browserDiagnostics.consoleIssues highlights just the warnings/errors, and browserDiagnostics.failedNetworkRequests highlights failed requests.",
|
|
562
569
|
]
|
|
563
570
|
: []),
|
|
564
|
-
...(
|
|
565
|
-
? [
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
571
|
+
...(!clipIsReady
|
|
572
|
+
? []
|
|
573
|
+
: isLoomEmbedBacked
|
|
574
|
+
? [
|
|
575
|
+
"This clip is a legacy Loom embed import; frame extraction is not available through Clips until it is reimported as a Clips-hosted video.",
|
|
576
|
+
]
|
|
577
|
+
: [
|
|
578
|
+
"This clip is readable as both text (transcript) and images (JPEG frames) — you can hear AND see it.",
|
|
579
|
+
"To SEE the screen, GET apis.frame.urlTemplate with atMs (returns image/jpeg). Start with recommendedFrames, then fetch additional frames around transcript timestamps that matter for the task.",
|
|
580
|
+
"If you cannot load an image from a URL, you will only have the transcript — tell the user to open the clip in an image-capable agent (ChatGPT, Claude Code, Cursor, Codex) or to upload a frame image directly so you can see it.",
|
|
581
|
+
]),
|
|
573
582
|
];
|
|
574
583
|
|
|
575
584
|
return {
|
|
@@ -593,13 +602,14 @@ export function buildPublicAgentContext({
|
|
|
593
602
|
hasAudio: Boolean(recording.hasAudio),
|
|
594
603
|
hasCamera: Boolean(recording.hasCamera),
|
|
595
604
|
status: recording.status,
|
|
605
|
+
agentReadiness,
|
|
596
606
|
createdAt: recording.createdAt,
|
|
597
607
|
updatedAt: recording.updatedAt,
|
|
598
608
|
},
|
|
599
609
|
apis: {
|
|
600
610
|
context: { method: "GET", url: api.contextUrl },
|
|
601
611
|
transcript: { method: "GET", url: api.transcriptUrl },
|
|
602
|
-
...(isLoomEmbedBacked
|
|
612
|
+
...(!clipIsReady || isLoomEmbedBacked
|
|
603
613
|
? {}
|
|
604
614
|
: {
|
|
605
615
|
frame: {
|
|
@@ -64,6 +64,7 @@ async function retypeBooleanColumnsOnPostgres(): Promise<void> {
|
|
|
64
64
|
["recording_viewers", "counted_view", false],
|
|
65
65
|
["recording_viewers", "cta_clicked", false],
|
|
66
66
|
["meeting_participants", "is_organizer", false],
|
|
67
|
+
["clips_meetings", "share_transcript", false],
|
|
67
68
|
];
|
|
68
69
|
for (const [table, column, defaultTrue] of alters) {
|
|
69
70
|
try {
|
|
@@ -833,6 +834,11 @@ const migrations = runMigrations(
|
|
|
833
834
|
`CREATE UNIQUE INDEX IF NOT EXISTS recording_viewers_recording_viewer_key_unique_idx ON recording_viewers (recording_id, viewer_key)`,
|
|
834
835
|
].join("; "),
|
|
835
836
|
},
|
|
837
|
+
{
|
|
838
|
+
version: 49,
|
|
839
|
+
name: "clips-meetings-share-transcript",
|
|
840
|
+
sql: `ALTER TABLE clips_meetings ADD COLUMN IF NOT EXISTS share_transcript INTEGER NOT NULL DEFAULT 0`,
|
|
841
|
+
},
|
|
836
842
|
],
|
|
837
843
|
{ table: "clips_migrations" },
|
|
838
844
|
);
|
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
type H3Event,
|
|
13
13
|
} from "h3";
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
buildAgentApiUrls,
|
|
17
|
+
getAgentClipReadiness,
|
|
18
|
+
} from "../../../shared/agent-context.js";
|
|
16
19
|
import { isLoomEmbedBackedRecording } from "../../../shared/loom.js";
|
|
17
20
|
import {
|
|
18
21
|
applyAgentJsonHeaders,
|
|
@@ -50,6 +53,8 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
50
53
|
token: accessResult.access.apiToken,
|
|
51
54
|
});
|
|
52
55
|
const isLoomEmbedBacked = isLoomEmbedBackedRecording(recording);
|
|
56
|
+
const agentReadiness = getAgentClipReadiness(recording.status);
|
|
57
|
+
const clipIsReady = agentReadiness.state === "ready";
|
|
53
58
|
|
|
54
59
|
return {
|
|
55
60
|
type: "agent-native.clip.transcript",
|
|
@@ -57,11 +62,13 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
57
62
|
id: recording.id,
|
|
58
63
|
title: recording.title,
|
|
59
64
|
durationMs: recording.durationMs,
|
|
65
|
+
status: recording.status,
|
|
66
|
+
agentReadiness,
|
|
60
67
|
},
|
|
61
68
|
apis: {
|
|
62
69
|
context: { method: "GET", url: api.contextUrl },
|
|
63
70
|
transcript: { method: "GET", url: api.transcriptUrl },
|
|
64
|
-
...(isLoomEmbedBacked
|
|
71
|
+
...(!clipIsReady || isLoomEmbedBacked
|
|
65
72
|
? {}
|
|
66
73
|
: {
|
|
67
74
|
frame: {
|
|
@@ -79,6 +86,9 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
79
86
|
segments: agentSegments,
|
|
80
87
|
segmentCount: agentSegments.length,
|
|
81
88
|
},
|
|
82
|
-
instructions:
|
|
89
|
+
instructions: [
|
|
90
|
+
...(agentReadiness.instruction ? [agentReadiness.instruction] : []),
|
|
91
|
+
...transcriptStatusInstructions(transcript),
|
|
92
|
+
],
|
|
83
93
|
};
|
|
84
94
|
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/public-meeting?id=<meetingId>
|
|
3
|
+
*
|
|
4
|
+
* Access-checked meeting notes for the anonymous share surface. Meeting access
|
|
5
|
+
* governs the payload; the linked transcript is an explicit, default-off part
|
|
6
|
+
* of that share and is omitted unless the meeting owner enables it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getSession, runWithRequestContext } from "@agent-native/core/server";
|
|
10
|
+
import { resolveAccess } from "@agent-native/core/sharing";
|
|
11
|
+
import { eq } from "drizzle-orm";
|
|
12
|
+
import {
|
|
13
|
+
defineEventHandler,
|
|
14
|
+
getQuery,
|
|
15
|
+
setResponseHeader,
|
|
16
|
+
setResponseStatus,
|
|
17
|
+
} from "h3";
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
normalizeTranscriptSegments,
|
|
21
|
+
parseTranscriptSegments,
|
|
22
|
+
} from "../../../shared/transcript-segments.js";
|
|
23
|
+
import { resolveTranscriptPresentation } from "../../../shared/transcript-status.js";
|
|
24
|
+
import { getDb, schema } from "../../db/index.js";
|
|
25
|
+
|
|
26
|
+
interface Bullet {
|
|
27
|
+
text: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseBullets(raw: string | null | undefined): Bullet[] {
|
|
31
|
+
if (!raw) return [];
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (!Array.isArray(parsed)) return [];
|
|
35
|
+
return parsed.filter(
|
|
36
|
+
(bullet): bullet is Bullet =>
|
|
37
|
+
typeof bullet === "object" &&
|
|
38
|
+
bullet !== null &&
|
|
39
|
+
typeof bullet.text === "string",
|
|
40
|
+
);
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default defineEventHandler(async (event) => {
|
|
47
|
+
setResponseHeader(event, "Cache-Control", "private, max-age=0, no-store");
|
|
48
|
+
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
49
|
+
|
|
50
|
+
const query = getQuery(event);
|
|
51
|
+
const meetingId = typeof query.id === "string" ? query.id : "";
|
|
52
|
+
if (!meetingId) {
|
|
53
|
+
setResponseStatus(event, 400);
|
|
54
|
+
return { error: "id is required" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const session = await getSession(event).catch(() => null);
|
|
58
|
+
const accessContext = {
|
|
59
|
+
userEmail: session?.email,
|
|
60
|
+
orgId: session?.orgId,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return runWithRequestContext(accessContext, async () => {
|
|
64
|
+
const access = await resolveAccess("meeting", meetingId, accessContext);
|
|
65
|
+
const meeting = access?.resource;
|
|
66
|
+
if (!meeting || meeting.trashedAt) {
|
|
67
|
+
setResponseStatus(event, 404);
|
|
68
|
+
return { error: "Not found" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const db = getDb();
|
|
72
|
+
const [participants, actionItems, transcriptRows] = await Promise.all([
|
|
73
|
+
db
|
|
74
|
+
.select({
|
|
75
|
+
email: schema.meetingParticipants.email,
|
|
76
|
+
name: schema.meetingParticipants.name,
|
|
77
|
+
isOrganizer: schema.meetingParticipants.isOrganizer,
|
|
78
|
+
})
|
|
79
|
+
.from(schema.meetingParticipants)
|
|
80
|
+
.where(eq(schema.meetingParticipants.meetingId, meetingId)),
|
|
81
|
+
db
|
|
82
|
+
.select({
|
|
83
|
+
id: schema.meetingActionItems.id,
|
|
84
|
+
text: schema.meetingActionItems.text,
|
|
85
|
+
assigneeEmail: schema.meetingActionItems.assigneeEmail,
|
|
86
|
+
completedAt: schema.meetingActionItems.completedAt,
|
|
87
|
+
})
|
|
88
|
+
.from(schema.meetingActionItems)
|
|
89
|
+
.where(eq(schema.meetingActionItems.meetingId, meetingId)),
|
|
90
|
+
meeting.shareTranscript && meeting.recordingId
|
|
91
|
+
? db
|
|
92
|
+
.select({
|
|
93
|
+
status: schema.recordingTranscripts.status,
|
|
94
|
+
language: schema.recordingTranscripts.language,
|
|
95
|
+
fullText: schema.recordingTranscripts.fullText,
|
|
96
|
+
failureReason: schema.recordingTranscripts.failureReason,
|
|
97
|
+
segmentsJson: schema.recordingTranscripts.segmentsJson,
|
|
98
|
+
updatedAt: schema.recordingTranscripts.updatedAt,
|
|
99
|
+
})
|
|
100
|
+
.from(schema.recordingTranscripts)
|
|
101
|
+
.where(
|
|
102
|
+
eq(schema.recordingTranscripts.recordingId, meeting.recordingId),
|
|
103
|
+
)
|
|
104
|
+
.limit(1)
|
|
105
|
+
: Promise.resolve([]),
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
const transcript = transcriptRows[0] ?? null;
|
|
109
|
+
const transcriptPresentation = resolveTranscriptPresentation(transcript);
|
|
110
|
+
const transcriptSegments = transcript
|
|
111
|
+
? normalizeTranscriptSegments({
|
|
112
|
+
segments: parseTranscriptSegments(transcript.segmentsJson),
|
|
113
|
+
fullText: transcript.fullText,
|
|
114
|
+
})
|
|
115
|
+
: [];
|
|
116
|
+
const role = access.role;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
meeting: {
|
|
120
|
+
id: meeting.id,
|
|
121
|
+
title: meeting.title,
|
|
122
|
+
scheduledStart: meeting.scheduledStart,
|
|
123
|
+
actualStart: meeting.actualStart,
|
|
124
|
+
actualEnd: meeting.actualEnd,
|
|
125
|
+
transcriptStatus: meeting.transcriptStatus,
|
|
126
|
+
summaryMd: meeting.summaryMd,
|
|
127
|
+
bullets: parseBullets(meeting.bulletsJson),
|
|
128
|
+
participants,
|
|
129
|
+
actionItems,
|
|
130
|
+
...(meeting.shareTranscript
|
|
131
|
+
? {
|
|
132
|
+
transcript: transcript
|
|
133
|
+
? {
|
|
134
|
+
status: transcriptPresentation.status,
|
|
135
|
+
language: transcript.language,
|
|
136
|
+
fullText: transcript.fullText,
|
|
137
|
+
segments: transcriptSegments,
|
|
138
|
+
}
|
|
139
|
+
: null,
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
142
|
+
},
|
|
143
|
+
viewer: session?.email
|
|
144
|
+
? {
|
|
145
|
+
role,
|
|
146
|
+
canEdit: role === "owner" || role === "admin" || role === "editor",
|
|
147
|
+
isOwner: role === "owner",
|
|
148
|
+
}
|
|
149
|
+
: null,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -13,6 +13,66 @@ export const AGENT_FRAME_ENDPOINT = "/api/agent-frame.jpg";
|
|
|
13
13
|
export const CLIP_AGENT_ACCESS_TOKEN_PREFIX = "clip-agent-context";
|
|
14
14
|
export const CLIPS_AGENT_ACCESS_PARAM = AGENT_ACCESS_PARAM || "agent_access";
|
|
15
15
|
|
|
16
|
+
export type AgentClipReadiness = {
|
|
17
|
+
state: "preparing" | "ready" | "failed";
|
|
18
|
+
retryAfterSeconds: number | null;
|
|
19
|
+
instruction: string | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function getAgentClipReadiness(
|
|
23
|
+
status: string | null | undefined,
|
|
24
|
+
): AgentClipReadiness {
|
|
25
|
+
if (status === "uploading") {
|
|
26
|
+
return {
|
|
27
|
+
state: "preparing",
|
|
28
|
+
retryAfterSeconds: 15,
|
|
29
|
+
instruction:
|
|
30
|
+
"This clip is still uploading and is not ready to inspect. Wait 15 seconds, then fetch agentContextUrl again. Do not open the share page, request frames, or draw conclusions until clip.status is ready.",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (status === "processing") {
|
|
35
|
+
return {
|
|
36
|
+
state: "preparing",
|
|
37
|
+
retryAfterSeconds: 15,
|
|
38
|
+
instruction:
|
|
39
|
+
"This clip is still processing and may still be transcoding or transcribing. Wait 15 seconds, then fetch agentContextUrl again. Do not open the share page, request frames, or draw conclusions until clip.status is ready.",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
state: status === "failed" ? "failed" : "ready",
|
|
45
|
+
retryAfterSeconds: null,
|
|
46
|
+
instruction: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildAgentDiscoveryPayload({
|
|
51
|
+
recordingId,
|
|
52
|
+
title,
|
|
53
|
+
status,
|
|
54
|
+
agentContextUrl,
|
|
55
|
+
}: {
|
|
56
|
+
recordingId: string;
|
|
57
|
+
title: string;
|
|
58
|
+
status: string | null | undefined;
|
|
59
|
+
agentContextUrl: string;
|
|
60
|
+
}) {
|
|
61
|
+
const readiness = getAgentClipReadiness(status);
|
|
62
|
+
return {
|
|
63
|
+
type: "agent-native.clip.discovery",
|
|
64
|
+
version: CLIP_AGENT_CONTEXT_VERSION,
|
|
65
|
+
clipId: recordingId,
|
|
66
|
+
title,
|
|
67
|
+
recordingStatus: status ?? "unknown",
|
|
68
|
+
agentReadiness: readiness,
|
|
69
|
+
agentContextUrl,
|
|
70
|
+
instructions:
|
|
71
|
+
readiness.instruction ??
|
|
72
|
+
"Fetch agentContextUrl for the transcript and JPEG frame URLs. Fetch the frame URLs to SEE the screen, not just read the transcript.",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
16
76
|
export function agentAccessTokenResourceId(recordingId: string): string {
|
|
17
77
|
if (typeof scopedAgentAccessResourceId !== "function") {
|
|
18
78
|
return `${CLIP_AGENT_ACCESS_TOKEN_PREFIX}:${recordingId}`;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const ZOOM_JOIN_PATH = /^\/j\/(\d+)\/?$/;
|
|
2
|
+
const ZOOM_WEB_CLIENT_JOIN_PATH = /^\/wc\/(\d+)\/join\/?$/;
|
|
3
|
+
|
|
4
|
+
function isZoomMeetingHost(hostname: string): boolean {
|
|
5
|
+
return hostname === "zoom.us" || hostname.endsWith(".zoom.us");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prefer Zoom's native URI when an HTTPS calendar link carries a meeting id.
|
|
10
|
+
* Other providers and URL shapes retain their original URL as the safe
|
|
11
|
+
* fallback.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveNativeMeetingJoinUrl(joinUrl: string): string {
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(joinUrl);
|
|
16
|
+
if (url.protocol !== "https:" || !isZoomMeetingHost(url.hostname)) {
|
|
17
|
+
return joinUrl;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const meetingNumber =
|
|
21
|
+
ZOOM_JOIN_PATH.exec(url.pathname)?.[1] ??
|
|
22
|
+
ZOOM_WEB_CLIENT_JOIN_PATH.exec(url.pathname)?.[1];
|
|
23
|
+
if (!meetingNumber) return joinUrl;
|
|
24
|
+
|
|
25
|
+
const params = new URLSearchParams({
|
|
26
|
+
action: "join",
|
|
27
|
+
confno: meetingNumber,
|
|
28
|
+
});
|
|
29
|
+
const passcode = url.searchParams.get("pwd");
|
|
30
|
+
if (passcode) params.set("pwd", passcode);
|
|
31
|
+
|
|
32
|
+
return `zoommtg://${url.hostname}/join?${params.toString()}`;
|
|
33
|
+
} catch {
|
|
34
|
+
return joinUrl;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
ok?: undefined;
|
|
30
29
|
error: string;
|
|
30
|
+
ok?: undefined;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -17,12 +17,12 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
17
17
|
id?: undefined;
|
|
18
18
|
provider?: undefined;
|
|
19
19
|
} | {
|
|
20
|
+
error?: undefined;
|
|
20
21
|
configured?: undefined;
|
|
21
22
|
connectPath?: undefined;
|
|
22
23
|
url: string;
|
|
23
24
|
id: string;
|
|
24
25
|
provider: string;
|
|
25
|
-
error?: undefined;
|
|
26
26
|
}>;
|
|
27
27
|
export default _default;
|
|
28
28
|
//# sourceMappingURL=upload-image.d.ts.map
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
14
|
count: number;
|
|
15
15
|
updated?: undefined;
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error?: undefined;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
count?: undefined;
|
|
20
20
|
updated: number;
|
|
21
|
-
ok?: undefined;
|
|
22
21
|
error?: undefined;
|
|
22
|
+
ok?: undefined;
|
|
23
23
|
} | {
|
|
24
24
|
count?: undefined;
|
|
25
25
|
updated?: undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.114.
|
|
3
|
+
"version": "0.114.5",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -380,7 +380,7 @@
|
|
|
380
380
|
"y-protocols": "^1.0.7",
|
|
381
381
|
"yjs": "^13.6.31",
|
|
382
382
|
"zod": "^4.3.6",
|
|
383
|
-
"@agent-native/recap-cli": "0.4.
|
|
383
|
+
"@agent-native/recap-cli": "0.4.5",
|
|
384
384
|
"@agent-native/toolkit": "^0.8.0"
|
|
385
385
|
},
|
|
386
386
|
"devDependencies": {
|