feat(google-meet): default artifacts to latest record

This commit is contained in:
Peter Steinberger
2026-04-25 08:07:48 +01:00
parent 459d277076
commit 4005a4f731
6 changed files with 52 additions and 3 deletions

View File

@@ -77,6 +77,7 @@ Docs: https://docs.openclaw.ai
- 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 markdown and file output for `googlemeet artifacts` and `googlemeet attendance` reports. Thanks @steipete.
- Plugins/Google Meet: add `googlemeet latest` plus matching tool/gateway actions to find the newest conference record for a meeting. Thanks @steipete.
- Plugins/Google Meet: make meeting-based artifact and attendance lookups use the latest conference record by default, with `--all-conference-records` for full history. 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.

View File

@@ -635,6 +635,10 @@ openclaw googlemeet artifacts --meeting https://meet.google.com/abc-defg-hij
openclaw googlemeet attendance --meeting https://meet.google.com/abc-defg-hij
```
With `--meeting`, `artifacts` and `attendance` use the latest conference record
by default. Pass `--all-conference-records` when you want every retained record
for that meeting.
If you already know the conference record id, address it directly:
```bash

View File

@@ -439,7 +439,7 @@ describe("google-meet plugin", () => {
);
});
it("lists Meet artifact metadata for conference records", async () => {
it("lists Meet artifact metadata for the latest conference record by default", async () => {
const fetchMock = stubMeetArtifactsApi();
await expect(
@@ -483,7 +483,7 @@ describe("google-meet plugin", () => {
}
const listUrl = requestUrl(listCall[0]);
expect(listUrl.searchParams.get("filter")).toBe('space.name = "spaces/abc-defg-hij"');
expect(listUrl.searchParams.get("pageSize")).toBe("2");
expect(listUrl.searchParams.get("pageSize")).toBe("1");
expect(fetchGuardMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://meet.googleapis.com/v2/conferenceRecords/rec-1/smartNotes?pageSize=2",
@@ -498,6 +498,28 @@ describe("google-meet plugin", () => {
);
});
it("keeps all conference records available when requested", async () => {
const fetchMock = stubMeetArtifactsApi();
await fetchGoogleMeetArtifacts({
accessToken: "token",
meeting: "abc-defg-hij",
pageSize: 2,
allConferenceRecords: true,
});
const listCall = fetchMock.mock.calls.find(([input]) => {
const url = requestUrl(input);
return url.pathname === "/v2/conferenceRecords";
});
if (!listCall) {
throw new Error("Expected conferenceRecords.list fetch call");
}
const listUrl = requestUrl(listCall[0]);
expect(listUrl.searchParams.get("pageSize")).toBe("2");
expect(listUrl.searchParams.get("filter")).toBe('space.name = "spaces/abc-defg-hij"');
});
it("fetches only the latest Meet conference record for a meeting", async () => {
const fetchMock = stubMeetArtifactsApi();

View File

@@ -191,6 +191,12 @@ const GoogleMeetToolSchema = Type.Object({
includeTranscriptEntries: Type.Optional(
Type.Boolean({ description: "For artifacts, include structured transcript entries" }),
),
includeAllConferenceRecords: Type.Optional(
Type.Boolean({
description:
"For artifacts or attendance with meeting input, fetch all conference records instead of only the latest.",
}),
),
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" })),
@@ -277,6 +283,7 @@ async function resolveArtifactQueryFromParams(
conferenceRecord,
pageSize: resolveOptionalPositiveInteger(raw.pageSize),
includeTranscriptEntries: raw.includeTranscriptEntries !== false,
allConferenceRecords: raw.includeAllConferenceRecords === true,
};
}
@@ -424,6 +431,7 @@ export default definePluginEntry({
conferenceRecord: resolved.conferenceRecord,
pageSize: resolved.pageSize,
includeTranscriptEntries: resolved.includeTranscriptEntries,
allConferenceRecords: resolved.allConferenceRecords,
}),
);
} catch (err) {
@@ -445,6 +453,7 @@ export default definePluginEntry({
meeting: resolved.meeting,
conferenceRecord: resolved.conferenceRecord,
pageSize: resolved.pageSize,
allConferenceRecords: resolved.allConferenceRecords,
}),
);
} catch (err) {
@@ -604,6 +613,7 @@ export default definePluginEntry({
conferenceRecord: resolved.conferenceRecord,
pageSize: resolved.pageSize,
includeTranscriptEntries: resolved.includeTranscriptEntries,
allConferenceRecords: resolved.allConferenceRecords,
}),
);
}
@@ -615,6 +625,7 @@ export default definePluginEntry({
meeting: resolved.meeting,
conferenceRecord: resolved.conferenceRecord,
pageSize: resolved.pageSize,
allConferenceRecords: resolved.allConferenceRecords,
}),
);
}

View File

@@ -55,6 +55,7 @@ type MeetArtifactOptions = ResolveSpaceOptions & {
conferenceRecord?: string;
pageSize?: string;
transcriptEntries?: boolean;
allConferenceRecords?: boolean;
format?: "summary" | "markdown";
output?: string;
};
@@ -445,6 +446,7 @@ function resolveArtifactTokenOptions(
expiresAt?: number;
pageSize?: number;
includeTranscriptEntries?: boolean;
allConferenceRecords?: boolean;
} {
const meeting = options.meeting?.trim() || config.defaults.meeting;
const conferenceRecord = options.conferenceRecord?.trim();
@@ -463,6 +465,7 @@ function resolveArtifactTokenOptions(
expiresAt: parseOptionalNumber(options.expiresAt) ?? config.oauth.expiresAt,
pageSize: parseOptionalNumber(options.pageSize),
includeTranscriptEntries: options.transcriptEntries !== false,
allConferenceRecords: Boolean(options.allConferenceRecords),
};
}
@@ -1030,6 +1033,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("--all-conference-records", "Fetch every conference record for --meeting")
.option("--no-transcript-entries", "Skip structured transcript entry lookup")
.option("--format <format>", "Output format: summary or markdown", "summary")
.option("--output <path>", "Write output to a file instead of stdout")
@@ -1043,6 +1047,7 @@ export function registerGoogleMeetCli(params: {
conferenceRecord: resolved.conferenceRecord,
pageSize: resolved.pageSize,
includeTranscriptEntries: resolved.includeTranscriptEntries,
allConferenceRecords: resolved.allConferenceRecords,
});
if (options.json) {
await writeCliOutput(
@@ -1083,6 +1088,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("--all-conference-records", "Fetch every conference record for --meeting")
.option("--format <format>", "Output format: summary or markdown", "summary")
.option("--output <path>", "Write output to a file instead of stdout")
.option("--json", "Print JSON output", false)
@@ -1094,6 +1100,7 @@ export function registerGoogleMeetCli(params: {
meeting: resolved.meeting,
conferenceRecord: resolved.conferenceRecord,
pageSize: resolved.pageSize,
allConferenceRecords: resolved.allConferenceRecords,
});
if (options.json) {
await writeCliOutput(

View File

@@ -532,6 +532,7 @@ async function resolveConferenceRecordQuery(params: {
meeting?: string;
conferenceRecord?: string;
pageSize?: number;
allConferenceRecords?: boolean;
}): Promise<{
input?: string;
space?: GoogleMeetSpace;
@@ -557,7 +558,8 @@ async function resolveConferenceRecordQuery(params: {
const conferenceRecords = await listGoogleMeetConferenceRecords({
accessToken: params.accessToken,
meeting: space.name,
pageSize: params.pageSize,
pageSize: params.allConferenceRecords ? params.pageSize : 1,
maxItems: params.allConferenceRecords ? undefined : 1,
});
return {
input: params.meeting,
@@ -572,6 +574,7 @@ export async function fetchGoogleMeetArtifacts(params: {
conferenceRecord?: string;
pageSize?: number;
includeTranscriptEntries?: boolean;
allConferenceRecords?: boolean;
}): Promise<GoogleMeetArtifactsResult> {
const resolved = await resolveConferenceRecordQuery(params);
const artifacts = await Promise.all(
@@ -652,6 +655,7 @@ export async function fetchGoogleMeetAttendance(params: {
meeting?: string;
conferenceRecord?: string;
pageSize?: number;
allConferenceRecords?: boolean;
}): Promise<GoogleMeetAttendanceResult> {
const resolved = await resolveConferenceRecordQuery(params);
const nestedRows = await Promise.all(