mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:50:42 +00:00
feat(google-meet): include transcript entries in artifacts
This commit is contained in:
@@ -73,7 +73,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/Google Meet: add a bundled participant plugin with personal Google auth, explicit meeting URL joins, Chrome and Twilio transports, and realtime voice support. (#70765) Thanks @steipete.
|
||||
- Plugins/Google Meet: default Chrome realtime sessions to OpenAI plus SoX `rec`/`play` audio bridge commands, so the usual setup only needs the plugin enabled and `OPENAI_API_KEY`. Thanks @steipete.
|
||||
- Plugins/Google Meet: add a `chrome-node` transport so a paired macOS node, such as a Parallels VM, can own Chrome, BlackHole, and SoX while the Gateway machine keeps the agent and model key. Thanks @steipete.
|
||||
- Plugins/Google Meet: add `googlemeet artifacts` and `googlemeet attendance` commands plus matching tool/gateway actions for conference records, recordings, transcripts, smart notes, and participant sessions. Thanks @steipete.
|
||||
- Plugins/Google Meet: add `googlemeet artifacts` and `googlemeet attendance` commands plus matching tool/gateway actions for conference records, recordings, transcripts and transcript entries, smart notes, and participant sessions. Thanks @steipete.
|
||||
- Plugins/Google Meet: add `googlemeet doctor --oauth` so operators can verify OAuth token refresh, Meet space reads, and side-effecting space creation without printing secrets. Thanks @steipete.
|
||||
- Plugins/Voice Call: expose the shared `openclaw_agent_consult` realtime tool so live phone calls can ask the full OpenClaw agent for deeper/tool-backed answers. Thanks @steipete.
|
||||
- Plugins/Voice Call: add `voicecall setup` and a dry-run-by-default `voicecall smoke` command so Twilio/provider readiness can be checked before placing a live test call. Thanks @steipete.
|
||||
|
||||
@@ -643,11 +643,12 @@ openclaw googlemeet attendance --conference-record conferenceRecords/abc123 --js
|
||||
```
|
||||
|
||||
`artifacts` returns conference record metadata plus participant, recording,
|
||||
transcript, and smart-note resource metadata when Google exposes it for the
|
||||
meeting. `attendance` expands participants into participant-session rows with
|
||||
join/leave timestamps. These commands use the Meet REST API only; transcript or
|
||||
smart-note document body download is intentionally out of scope because that
|
||||
requires separate Google Docs/Drive access.
|
||||
transcript, structured transcript-entry, and smart-note resource metadata when
|
||||
Google exposes it for the meeting. Use `--no-transcript-entries` to skip
|
||||
entry lookup for large meetings. `attendance` expands participants into
|
||||
participant-session rows with join/leave timestamps. These commands use the Meet
|
||||
REST API only; Google Docs/Drive document body download is intentionally out of
|
||||
scope because that requires separate Google Docs/Drive access.
|
||||
|
||||
Create a fresh Meet space:
|
||||
|
||||
|
||||
@@ -156,6 +156,20 @@ function stubMeetArtifactsApi() {
|
||||
],
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/v2/conferenceRecords/rec-1/transcripts/t1/entries") {
|
||||
return jsonResponse({
|
||||
transcriptEntries: [
|
||||
{
|
||||
name: "conferenceRecords/rec-1/transcripts/t1/entries/e1",
|
||||
participant: "conferenceRecords/rec-1/participants/p1",
|
||||
text: "Hello from the transcript.",
|
||||
languageCode: "en-US",
|
||||
startTime: "2026-04-25T10:01:00Z",
|
||||
endTime: "2026-04-25T10:01:05Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/v2/conferenceRecords/rec-1/smartNotes") {
|
||||
return jsonResponse({
|
||||
smartNotes: [
|
||||
@@ -439,6 +453,17 @@ describe("google-meet plugin", () => {
|
||||
participants: [{ name: "conferenceRecords/rec-1/participants/p1" }],
|
||||
recordings: [{ name: "conferenceRecords/rec-1/recordings/r1" }],
|
||||
transcripts: [{ name: "conferenceRecords/rec-1/transcripts/t1" }],
|
||||
transcriptEntries: [
|
||||
{
|
||||
transcript: "conferenceRecords/rec-1/transcripts/t1",
|
||||
entries: [
|
||||
{
|
||||
name: "conferenceRecords/rec-1/transcripts/t1/entries/e1",
|
||||
text: "Hello from the transcript.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
smartNotes: [{ name: "conferenceRecords/rec-1/smartNotes/sn1" }],
|
||||
},
|
||||
],
|
||||
@@ -460,6 +485,12 @@ describe("google-meet plugin", () => {
|
||||
auditContext: "google-meet.conferenceRecords.smartNotes.list",
|
||||
}),
|
||||
);
|
||||
expect(fetchGuardMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
url: "https://meet.googleapis.com/v2/conferenceRecords/rec-1/transcripts/t1/entries?pageSize=2",
|
||||
auditContext: "google-meet.conferenceRecords.transcripts.entries.list",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("lists Meet attendance rows with participant sessions", async () => {
|
||||
@@ -868,6 +899,12 @@ describe("google-meet plugin", () => {
|
||||
{
|
||||
recordings: [{ name: "conferenceRecords/rec-1/recordings/r1" }],
|
||||
transcripts: [{ name: "conferenceRecords/rec-1/transcripts/t1" }],
|
||||
transcriptEntries: [
|
||||
{
|
||||
transcript: "conferenceRecords/rec-1/transcripts/t1",
|
||||
entries: [{ text: "Hello from the transcript." }],
|
||||
},
|
||||
],
|
||||
smartNotes: [{ name: "conferenceRecords/rec-1/smartNotes/sn1" }],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -186,6 +186,9 @@ const GoogleMeetToolSchema = Type.Object({
|
||||
Type.String({ description: "Meet conferenceRecords/{id} resource name or id" }),
|
||||
),
|
||||
pageSize: Type.Optional(Type.Number({ description: "Meet API page size for list actions" })),
|
||||
includeTranscriptEntries: Type.Optional(
|
||||
Type.Boolean({ description: "For artifacts, include structured transcript entries" }),
|
||||
),
|
||||
accessToken: Type.Optional(Type.String({ description: "Access token override" })),
|
||||
refreshToken: Type.Optional(Type.String({ description: "Refresh token override" })),
|
||||
clientId: Type.Optional(Type.String({ description: "OAuth client id override" })),
|
||||
@@ -271,6 +274,7 @@ async function resolveArtifactQueryFromParams(
|
||||
meeting,
|
||||
conferenceRecord,
|
||||
pageSize: resolveOptionalPositiveInteger(raw.pageSize),
|
||||
includeTranscriptEntries: raw.includeTranscriptEntries !== false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -397,6 +401,7 @@ export default definePluginEntry({
|
||||
meeting: resolved.meeting,
|
||||
conferenceRecord: resolved.conferenceRecord,
|
||||
pageSize: resolved.pageSize,
|
||||
includeTranscriptEntries: resolved.includeTranscriptEntries,
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -566,6 +571,7 @@ export default definePluginEntry({
|
||||
meeting: resolved.meeting,
|
||||
conferenceRecord: resolved.conferenceRecord,
|
||||
pageSize: resolved.pageSize,
|
||||
includeTranscriptEntries: resolved.includeTranscriptEntries,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ type ResolveSpaceOptions = {
|
||||
type MeetArtifactOptions = ResolveSpaceOptions & {
|
||||
conferenceRecord?: string;
|
||||
pageSize?: string;
|
||||
transcriptEntries?: boolean;
|
||||
};
|
||||
|
||||
type SetupOptions = {
|
||||
@@ -429,6 +430,7 @@ function resolveArtifactTokenOptions(
|
||||
accessToken?: string;
|
||||
expiresAt?: number;
|
||||
pageSize?: number;
|
||||
includeTranscriptEntries?: boolean;
|
||||
} {
|
||||
const meeting = options.meeting?.trim() || config.defaults.meeting;
|
||||
const conferenceRecord = options.conferenceRecord?.trim();
|
||||
@@ -446,6 +448,7 @@ function resolveArtifactTokenOptions(
|
||||
accessToken: options.accessToken?.trim() || config.oauth.accessToken,
|
||||
expiresAt: parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt,
|
||||
pageSize: parseOptionalNumber(options.pageSize),
|
||||
includeTranscriptEntries: options.transcriptEntries !== false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -474,6 +477,10 @@ function writeArtifactsSummary(result: GoogleMeetArtifactsResult): void {
|
||||
writeStdoutLine("participants: %d", entry.participants.length);
|
||||
writeStdoutLine("recordings: %d", entry.recordings.length);
|
||||
writeStdoutLine("transcripts: %d", entry.transcripts.length);
|
||||
writeStdoutLine(
|
||||
"transcript entries: %d",
|
||||
entry.transcriptEntries.reduce((count, transcript) => count + transcript.entries.length, 0),
|
||||
);
|
||||
writeStdoutLine("smart notes: %d", entry.smartNotes.length);
|
||||
if (entry.smartNotesError) {
|
||||
writeStdoutLine("smart notes warning: %s", entry.smartNotesError);
|
||||
@@ -484,6 +491,15 @@ function writeArtifactsSummary(result: GoogleMeetArtifactsResult): void {
|
||||
for (const transcript of entry.transcripts) {
|
||||
writeStdoutLine("- transcript: %s", transcript.name);
|
||||
}
|
||||
for (const transcriptEntries of entry.transcriptEntries) {
|
||||
if (transcriptEntries.entriesError) {
|
||||
writeStdoutLine(
|
||||
"- transcript entries warning: %s: %s",
|
||||
transcriptEntries.transcript,
|
||||
transcriptEntries.entriesError,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const smartNote of entry.smartNotes) {
|
||||
writeStdoutLine("- smart note: %s", smartNote.name);
|
||||
}
|
||||
@@ -840,6 +856,7 @@ export function registerGoogleMeetCli(params: {
|
||||
.option("--client-secret <secret>", "OAuth client secret override")
|
||||
.option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds")
|
||||
.option("--page-size <n>", "Max resources per Meet API page")
|
||||
.option("--no-transcript-entries", "Skip structured transcript entry lookup")
|
||||
.option("--json", "Print JSON output", false)
|
||||
.action(async (options: MeetArtifactOptions) => {
|
||||
const resolved = resolveArtifactTokenOptions(params.config, options);
|
||||
@@ -849,6 +866,7 @@ export function registerGoogleMeetCli(params: {
|
||||
meeting: resolved.meeting,
|
||||
conferenceRecord: resolved.conferenceRecord,
|
||||
pageSize: resolved.pageSize,
|
||||
includeTranscriptEntries: resolved.includeTranscriptEntries,
|
||||
});
|
||||
if (options.json) {
|
||||
writeStdoutJson({
|
||||
|
||||
@@ -73,6 +73,21 @@ export type GoogleMeetTranscript = {
|
||||
docsDestination?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type GoogleMeetTranscriptEntry = {
|
||||
name: string;
|
||||
participant?: string;
|
||||
text?: string;
|
||||
languageCode?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
};
|
||||
|
||||
export type GoogleMeetTranscriptEntries = {
|
||||
transcript: string;
|
||||
entries: GoogleMeetTranscriptEntry[];
|
||||
entriesError?: string;
|
||||
};
|
||||
|
||||
export type GoogleMeetSmartNote = {
|
||||
name: string;
|
||||
startTime?: string;
|
||||
@@ -85,6 +100,7 @@ export type GoogleMeetArtifactsEntry = {
|
||||
participants: GoogleMeetParticipant[];
|
||||
recordings: GoogleMeetRecording[];
|
||||
transcripts: GoogleMeetTranscript[];
|
||||
transcriptEntries: GoogleMeetTranscriptEntries[];
|
||||
smartNotes: GoogleMeetSmartNote[];
|
||||
smartNotesError?: string;
|
||||
};
|
||||
@@ -434,6 +450,21 @@ export async function listGoogleMeetTranscripts(params: {
|
||||
});
|
||||
}
|
||||
|
||||
export async function listGoogleMeetTranscriptEntries(params: {
|
||||
accessToken: string;
|
||||
transcript: string;
|
||||
pageSize?: number;
|
||||
}): Promise<GoogleMeetTranscriptEntry[]> {
|
||||
return listGoogleMeetCollection<GoogleMeetTranscriptEntry>({
|
||||
accessToken: params.accessToken,
|
||||
path: `${encodeResourceNameForPath(params.transcript)}/entries`,
|
||||
collectionKey: "transcriptEntries",
|
||||
query: { pageSize: params.pageSize },
|
||||
auditContext: "google-meet.conferenceRecords.transcripts.entries.list",
|
||||
errorPrefix: "Google Meet conferenceRecords.transcripts.entries.list",
|
||||
});
|
||||
}
|
||||
|
||||
export async function listGoogleMeetSmartNotes(params: {
|
||||
accessToken: string;
|
||||
conferenceRecord: string;
|
||||
@@ -506,6 +537,7 @@ export async function fetchGoogleMeetArtifacts(params: {
|
||||
meeting?: string;
|
||||
conferenceRecord?: string;
|
||||
pageSize?: number;
|
||||
includeTranscriptEntries?: boolean;
|
||||
}): Promise<GoogleMeetArtifactsResult> {
|
||||
const resolved = await resolveConferenceRecordQuery(params);
|
||||
const artifacts = await Promise.all(
|
||||
@@ -537,11 +569,35 @@ export async function fetchGoogleMeetArtifacts(params: {
|
||||
smartNotesError: getErrorMessage(error),
|
||||
})),
|
||||
]);
|
||||
const transcriptEntries =
|
||||
params.includeTranscriptEntries === false
|
||||
? []
|
||||
: await Promise.all(
|
||||
transcripts.map(async (transcript) => {
|
||||
try {
|
||||
return {
|
||||
transcript: transcript.name,
|
||||
entries: await listGoogleMeetTranscriptEntries({
|
||||
accessToken: params.accessToken,
|
||||
transcript: transcript.name,
|
||||
pageSize: params.pageSize,
|
||||
}),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
transcript: transcript.name,
|
||||
entries: [],
|
||||
entriesError: getErrorMessage(error),
|
||||
};
|
||||
}
|
||||
}),
|
||||
);
|
||||
return {
|
||||
conferenceRecord,
|
||||
participants,
|
||||
recordings,
|
||||
transcripts,
|
||||
transcriptEntries,
|
||||
smartNotes: smartNotesResult.smartNotes,
|
||||
...(smartNotesResult.smartNotesError
|
||||
? { smartNotesError: smartNotesResult.smartNotesError }
|
||||
|
||||
Reference in New Issue
Block a user