Repository navigation
feat: Each recording knows its meeting, and an assistant lists the recordings of a meeting EXO-91998 - #429
Conversation
…cordings of a meeting - EXO-91998 The recording upload gets its own size limit (exo.webconferencing.recording.uploadLimitMB, 0 = unlimited by default) instead of the platform's generic one, which refuses the recording of a long meeting. Every recording saved from now on carries the new exo:callRecording mixin (call id, provider, owner, title, scheduled start, recording date) and is titled after its meeting and date; its file name is unchanged. CallProvider gains an optional findCallId(url). WebConferencingService.getCallRecordings lists a call's recordings with a JCR-SQL query in the viewer's own session, so the repository's permissions decide what comes back, after checking that the viewer is a member of the call's space (space and space-event calls) or a participant (other calls). A recording saved before the mixin is found by its Jibri file name, and merged with the stamped ones. The end of a range is extended by exo.webconferencing.recording.occurrenceGraceHours (12). Spring MVC REST: GET /webconferencing/rest/calls/{callId}/recordings and GET /webconferencing/rest/recordings/byConference?url=, both with optional from/to. MCP tool get_meeting_recordings (read-only), the add-on's first, with its own ai-tool-definitions.json and the -parameters compiler flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s failure - EXO-91998 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…der gives its file - EXO-91998 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…XO-91998 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e calling thread runs as - EXO-91998 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng that defaults to UTC - EXO-91998 Jibri names a recording after its JVM's local time, with no zone. exo.webconferencing.recording.recorderTimeZone names that zone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…me zone - EXO-91998 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed - EXO-91998 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Self-review close-out (AI, rounds 2–4 of the delivery #429 · #430 · #431 · jitsi#173 · agenda#1241 · agenda#1242 · ppd#648)Rounds after the first fixes: delivery checks ×3, a fresh reviewer (round 2), a fresh closing reviewer (rounds 3–4, clean).
Verified conform (closing reviewer, in source): the read as the viewer, traced through the JCR engine ( Tests at This change is classified N1 at |
…recordings of a relative date - EXO-91998 get_meeting_recordings answered a date it could not read, such as "yesterday", with the REST layer's bare message code, so the model had nothing to correct its call with. The tool now refuses it with a message naming the argument, the accepted formats with an example, and asking the model to resolve the relative expression itself; the date arguments' descriptions state the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 9118d3e2e69bc598b8bd7167ccab65c2f7c04b34)
#432) ## Why EXO-92126: the people who attended the call a recording was made in are the first candidates for who its speakers are. ## How - `WebConferencingService.getRecordingAttendees(documentId)`: the attendees the recording's call stored; null (no attendee list) when the call lists none or more than `MAX_RECORDING_ATTENDEES` (20), is a space call or a meeting that invited a space, or is no longer known. - `CallRecordingStorage.findCallId(documentId)`: the call a stamped recording was made in, read as the system. - Read by the enterprise glue (platform-private-distributions, EXO-92126), so documents does not depend on web-conferencing. Stacked on #429 (EXO-91998). Knowledge: Meeds-io/eng-standards#756 This change is classified N1 at head 6d4c838 (a system read of call data, part of the EXO-92126 delivery) — its approver must be an Archi/Dev who knows it is N1, not an approval on AI review alone; author ≠ approver. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…nda EXO-92121 (#434) ## Why A meeting recording opened in Documents shows its length and transcript, but not the meeting it was recorded in (EXO-92121). The details panel now shows « Meeting: <event title> · <date> », opening the event in the agenda, or the occurrence of a recurring one. Stacked on #429 (the recording stamp `exo:callRecording` and `CallRecordingStorage`). ## How - `GET /webconferencing/rest/recordings/documents/{documentId}/call`: reads the file in the user's own JCR session (`readAs`, shared with `findRecordings`) and answers its stamp: `callId`, `providerType`, `ownerType`, `startedDate`, `recordedDate`, plus `graceHours`. A link to a recording answers its target's call. A file the user cannot read, in the trash, or without the stamp (any other file, a recording saved before stamping) answers 204. Every answer is `no-store`. - `occurrence-grace-hours` is capped at 168 h (with a WARN), the longest the agenda takes, so both read the same occurrence. - The line is a Vue component in a new `WebConferencingDocumentsExtension` module (`DocumentsGRP`), contributed to the details rows (`DocumentsInfoDetails` / `documents-info-details`, exoplatform/documents#2130). It shows the event's title on a first line and its date on a second one, its label aligned with the title (`align-top`). Only for an agenda-event call (`ownerType` `space_event`), it asks exoplatform/agenda#1244 for the event at the recording's save date with that grace; it shows nothing otherwise. No provider script is needed on the page. - Labels in `Visio_en.properties` only. - Scope: the line shows in the Documents app only (decided on the task); the details panel opened elsewhere (stream, notes, favorites) does not load the module. A call not started from an agenda event shows no line. Tests: `CallRecordingStorageTest`, `WebConferencingServiceRecordingsTest`, `CallRecordingRestTest` (new cases mutation-verified); full `web-conferencing-services` suite with `-Pcoverage`: 82/82; webapp build and lint clean. Knowledge: Meeds-io/eng-standards#761 Classification: N1 (a client-supplied document id read in a swapped ConversationState behind a new REST) — computed at a520552. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…-92130 (#433) ## Why EXO-92130: when an assistant lists a meeting's recordings, each recording shows its chapters and its speakers, so that the assistant picks the right recording and chapter before reading any transcript. ## How - `get_meeting_recordings` gives, for the 10 newest recordings it lists, the chapters of the transcript (start in seconds, time, title) and the speakers' names in the order they first speak: the person's full name, the guest's name, or `Speaker N` (`RecordingOutlineReader`, `RecordingChapterModel`, `RecordingOutlineModel`). A recording without them is listed as before (`NON_EMPTY`). - The outline is read from Documents (`DocumentFileService#getTranscriptionChapters`, `#getTranscriptionSegments`, `#getTranscriptionSpeakers`) with the listing user's identity, so Documents' ACL applies. Chapters and speakers are read separately: a failure on one never takes the other away, and a failure on both lists the recording without an outline. - `documents-api` is a `provided` dependency, and the reader is a bean only when Documents is installed (`@ConditionalOnClass` by name, `ObjectProvider` in the tool): without Documents, recordings are listed as today. - Chapters are numbered as Documents numbers them (title and start present, stored order), and the tool description sends the assistant to `get_transcript_excerpt` (documents) with the recording's `documentId` and the chapter's number. ## Tests - `RecordingOutlineReaderTest`, `MeetingRecordingMcpToolTest`: 22 tests, 0 failures, compiled against a `documents-api` carrying #2121 and #2122 built locally; this PR's CI needs a published `documents-api` with both, so it waits for those PRs. - On the shared test server, through an MCP client (Claude Code) connected to the server's MCP endpoint, not the EVA chat: `get_meeting_recordings` on the « Demo ACME recordings » meeting listed its 13 recordings, the ones with chapters showing them. Asked in the EVA chat by a recording's title, EVA hands the question to its Search or Content specialist and never reaches this tool: routing meeting questions is EXO-92131's. The tool description changed: an instance that already imported it shows the new text after one start with `meeds.mcp.tools.forceReimport=true`. Stacked on #429 (EXO-91998, `get_meeting_recordings`). Knowledge: pending — EXO-92130 This change is classified N1 (the user's identity resolution drives the ACL'd Documents reads, recall-first) at 4093eb0 — its approver must be an Archi/Dev who knows it is N1, not an approval on AI review alone; author ≠ approver. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#432) ## Why EXO-92126: the people who attended the call a recording was made in are the first candidates for who its speakers are. ## How - `WebConferencingService.getRecordingAttendees(documentId)`: the attendees the recording's call stored; null (no attendee list) when the call lists none or more than `MAX_RECORDING_ATTENDEES` (20), is a space call or a meeting that invited a space, or is no longer known. - `CallRecordingStorage.findCallId(documentId)`: the call a stamped recording was made in, read as the system. - Read by the enterprise glue (platform-private-distributions, EXO-92126), so documents does not depend on web-conferencing. Stacked on #429 (EXO-91998). Knowledge: Meeds-io/eng-standards#756 This change is classified N1 at head 6d4c838 (a system read of call data, part of the EXO-92126 delivery) — its approver must be an Archi/Dev who knows it is N1, not an approval on AI review alone; author ≠ approver. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…nda EXO-92121 (#434) ## Why A meeting recording opened in Documents shows its length and transcript, but not the meeting it was recorded in (EXO-92121). The details panel now shows « Meeting: <event title> · <date> », opening the event in the agenda, or the occurrence of a recurring one. Stacked on #429 (the recording stamp `exo:callRecording` and `CallRecordingStorage`). ## How - `GET /webconferencing/rest/recordings/documents/{documentId}/call`: reads the file in the user's own JCR session (`readAs`, shared with `findRecordings`) and answers its stamp: `callId`, `providerType`, `ownerType`, `startedDate`, `recordedDate`, plus `graceHours`. A link to a recording answers its target's call. A file the user cannot read, in the trash, or without the stamp (any other file, a recording saved before stamping) answers 204. Every answer is `no-store`. - `occurrence-grace-hours` is capped at 168 h (with a WARN), the longest the agenda takes, so both read the same occurrence. - The line is a Vue component in a new `WebConferencingDocumentsExtension` module (`DocumentsGRP`), contributed to the details rows (`DocumentsInfoDetails` / `documents-info-details`, exoplatform/documents#2130). It shows the event's title on a first line and its date on a second one, its label aligned with the title (`align-top`). Only for an agenda-event call (`ownerType` `space_event`), it asks exoplatform/agenda#1244 for the event at the recording's save date with that grace; it shows nothing otherwise. No provider script is needed on the page. - Labels in `Visio_en.properties` only. - Scope: the line shows in the Documents app only (decided on the task); the details panel opened elsewhere (stream, notes, favorites) does not load the module. A call not started from an agenda event shows no line. Tests: `CallRecordingStorageTest`, `WebConferencingServiceRecordingsTest`, `CallRecordingRestTest` (new cases mutation-verified); full `web-conferencing-services` suite with `-Pcoverage`: 82/82; webapp build and lint clean. Knowledge: Meeds-io/eng-standards#761 Classification: N1 (a client-supplied document id read in a swapped ConversationState behind a new REST) — computed at a520552. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…-92130 (#433) ## Why EXO-92130: when an assistant lists a meeting's recordings, each recording shows its chapters and its speakers, so that the assistant picks the right recording and chapter before reading any transcript. ## How - `get_meeting_recordings` gives, for the 10 newest recordings it lists, the chapters of the transcript (start in seconds, time, title) and the speakers' names in the order they first speak: the person's full name, the guest's name, or `Speaker N` (`RecordingOutlineReader`, `RecordingChapterModel`, `RecordingOutlineModel`). A recording without them is listed as before (`NON_EMPTY`). - The outline is read from Documents (`DocumentFileService#getTranscriptionChapters`, `#getTranscriptionSegments`, `#getTranscriptionSpeakers`) with the listing user's identity, so Documents' ACL applies. Chapters and speakers are read separately: a failure on one never takes the other away, and a failure on both lists the recording without an outline. - `documents-api` is a `provided` dependency, and the reader is a bean only when Documents is installed (`@ConditionalOnClass` by name, `ObjectProvider` in the tool): without Documents, recordings are listed as today. - Chapters are numbered as Documents numbers them (title and start present, stored order), and the tool description sends the assistant to `get_transcript_excerpt` (documents) with the recording's `documentId` and the chapter's number. ## Tests - `RecordingOutlineReaderTest`, `MeetingRecordingMcpToolTest`: 22 tests, 0 failures, compiled against a `documents-api` carrying #2121 and #2122 built locally; this PR's CI needs a published `documents-api` with both, so it waits for those PRs. - On the shared test server, through an MCP client (Claude Code) connected to the server's MCP endpoint, not the EVA chat: `get_meeting_recordings` on the « Demo ACME recordings » meeting listed its 13 recordings, the ones with chapters showing them. Asked in the EVA chat by a recording's title, EVA hands the question to its Search or Content specialist and never reaches this tool: routing meeting questions is EXO-92131's. The tool description changed: an instance that already imported it shows the new text after one start with `meeds.mcp.tools.forceReimport=true`. Stacked on #429 (EXO-91998, `get_meeting_recordings`). Knowledge: pending — EXO-92130 This change is classified N1 (the user's identity resolution drives the ACL'd Documents reads, recall-first) at 4093eb0 — its approver must be an Archi/Dev who knows it is N1, not an approval on AI review alone; author ≠ approver. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Why
After a meeting held in eXo's video meeting, its recordings must be found from the meeting itself, by the agenda and by the assistant, with the viewer's own rights (EXO-91998; Tech Spec note 55129 §2.2, §2.3, §3.2, §3.5).
How
uploadFile: the recording upload carries its own limit,exo.webconferencing.recording.uploadLimitMB(0 = unlimited by default), registered under a random upload id and removed infinally; the failure path now passes the upload info, so a refused recording is notified.saveFile: new optional mixinexo:callRecording(conf/webconferencing-nodetypes.xml) stamps the call id, provider, owner, title, start and save dates;exo:titlebecomes « call title – date », the node name is unchanged.CallProvider.findCallId(url): new optional SPI method,nullby default.WebConferencingService.getCallRecordings/getCallRecordingsByConference, instorage/CallRecordingStorage: call-level check (space membership or super manager; owner or participant), then a JCR-SQL query run as the viewer (made the current conversation state around the session), merged with the unstamped files named after the call;exo.webconferencing.recording.occurrenceGraceHours(12) is added toto. Each recording carriesstartedDate, read from the recorder's file name in the recorder's time zone,exo.webconferencing.recording.recorderTimeZone(defaultUTC; an invalid value falls back to UTC with a warning): Jibri writes its JVM's local time with no zone, and a local time repeated when clocks go back reads as its earlier instant.GET /webconferencing/rest/calls/{callId}/recordingsandGET /webconferencing/rest/recordings/byConference?url=, optionalfrom/to; fixed message codes on 400/403/404/500,Cache-Control: no-store.get_meeting_recordings(conference_url, recorded_from?, recorded_to?), read-only:MeetingRecordingMcpTool, itsai-tool-definitions.jsonentry,mcp-server-toolsas a provided dependency and the-parametersflag. A date it cannot read (RFC3339 oryyyy-MM-ddonly) is refused with a message naming the argument and the accepted formats.The jitsi PR implementing
findCallIdcompiles once this one is merged and published.Upgrade / Ops: where the Jibri JVM does not run in UTC, set
exo.webconferencing.recording.recorderTimeZoneto its zone (e.g.Europe/Paris); otherwise every recording's start is shifted by that offset. Recording uploads are no longer capped byexo.uploadLimit(200 MB by default): setexo.webconferencing.recording.uploadLimitMBto bound them.Knowledge: Meeds-io/eng-standards#695, Meeds-io/eng-standards#696
This change is classified N1 at
842b1090— its approver must be an Archi/Dev who knows it is N1, not an approval on AI review alone; author ≠ approver.🤖 Generated with Claude Code