{% endif %}
- {% if app_settings.enable_retention_policy_group %}
-
-
-
- {% endif %}
@@ -862,78 +846,6 @@
Group Workspace
{% endif %}
- {% if app_settings.enable_retention_policy_group %}
-
-
-
-
Retention Policy Settings
-
Configure how long to keep conversations and documents in this group workspace. Items older than the specified period will be automatically deleted.
-
-
-
- Default: You can use the organization default or set a custom retention period. Choose "No automatic deletion" to keep items indefinitely.
-
-
-
-
-
-
- Conversations older than this will be automatically deleted.
-
-
-
-
-
- Documents older than this will be automatically deleted.
-
-
-
-
-
-
-
-
-
-
- Important: Deleted conversations will be archived if archiving is enabled. All deletions are logged in activity history.
-
-
-
-
- {% endif %}
@@ -2553,7 +2465,6 @@
Select Tags
// Update UI elements dependent on role (applies to both tabs potentially)
updateRoleDisplay();
updateGroupPromptsRoleUI(); // This is specific to prompts tab UI elements
- loadGroupRetentionSettings(); // Load retention settings
}
function updateRoleDisplay() {
@@ -2585,140 +2496,9 @@
Select Tags
uploadSection.style.display = showUpload ? "block" : "none";
if (uploadHr) uploadHr.style.display = showUpload ? "block" : "none";
- // Control visibility of Settings tab (only for Owners and Admins)
- const settingsTabNav = document.getElementById('group-settings-tab-nav');
- const canManageSettings = ['Owner', 'Admin'].includes(userRoleInActiveGroup);
- if (settingsTabNav) {
- settingsTabNav.classList.toggle('d-none', !canManageSettings);
- }
-
notifyGroupWorkspaceContext();
}
- /* ===================== GROUP RETENTION POLICY ===================== */
-
- async function loadGroupRetentionSettings() {
- if (!activeGroupId) return;
-
- const convSelect = document.getElementById('group-conversation-retention-days');
- const docSelect = document.getElementById('group-document-retention-days');
-
- if (!convSelect || !docSelect) return; // Settings tab not available
-
- console.log('Loading group retention settings for:', activeGroupId);
-
- try {
- // Fetch organization defaults for group retention
- const orgDefaultsResp = await fetch('/api/retention-policy/defaults/group');
- const orgData = await orgDefaultsResp.json();
-
- if (orgData.success) {
- const convDefaultOption = convSelect.querySelector('option[value="default"]');
- const docDefaultOption = docSelect.querySelector('option[value="default"]');
-
- if (convDefaultOption) {
- convDefaultOption.textContent = `Using organization default (${orgData.default_conversation_label})`;
- }
- if (docDefaultOption) {
- docDefaultOption.textContent = `Using organization default (${orgData.default_document_label})`;
- }
- console.log('Loaded org defaults:', orgData);
- }
- } catch (error) {
- console.error('Error loading group retention defaults:', error);
- }
-
- // Load current group's retention policy settings
- try {
- const groupResp = await fetch(`/api/groups/${activeGroupId}`);
-
- if (!groupResp.ok) {
- throw new Error(`Failed to fetch group: ${groupResp.status}`);
- }
-
- const groupData = await groupResp.json();
- console.log('Loaded group data:', groupData);
-
- // API returns group object directly (not wrapped in success/group)
- if (groupData && groupData.retention_policy) {
- const retentionPolicy = groupData.retention_policy;
- let convRetention = retentionPolicy.conversation_retention_days;
- let docRetention = retentionPolicy.document_retention_days;
-
- console.log('Found retention policy:', retentionPolicy);
-
- // If undefined, use 'default'
- if (convRetention === undefined || convRetention === null) convRetention = 'default';
- if (docRetention === undefined || docRetention === null) docRetention = 'default';
-
- convSelect.value = convRetention;
- docSelect.value = docRetention;
- console.log('Set retention values to:', { conv: convRetention, doc: docRetention });
- } else {
- // Set to organization default if no retention policy set
- console.log('No retention policy found, using defaults');
- convSelect.value = 'default';
- docSelect.value = 'default';
- }
- } catch (error) {
- console.error('Error loading group retention settings:', error);
- // Set defaults on error
- convSelect.value = 'default';
- docSelect.value = 'default';
- }
- }
-
- async function saveGroupRetentionSettings() {
- if (!activeGroupId) {
- showToast('No active group selected.', 'warning');
- return;
- }
-
- const convSelect = document.getElementById('group-conversation-retention-days');
- const docSelect = document.getElementById('group-document-retention-days');
- const statusSpan = document.getElementById('group-retention-save-status');
-
- if (!convSelect || !docSelect) return;
-
- const retentionData = {
- conversation_retention_days: convSelect.value,
- document_retention_days: docSelect.value
- };
-
- console.log('Saving group retention settings:', retentionData);
-
- // Show saving status
- if (statusSpan) {
- statusSpan.innerHTML = ' Saving...';
- }
-
- try {
- const response = await fetch(`/api/retention-policy/group/${activeGroupId}`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(retentionData)
- });
-
- const data = await response.json();
- console.log('Save response:', data);
-
- if (response.ok && data.success) {
- if (statusSpan) {
- statusSpan.innerHTML = ' Saved successfully!';
- setTimeout(() => { statusSpan.innerHTML = ''; }, 3000);
- }
- console.log('Group retention settings saved successfully');
- } else {
- throw new Error(data.error || 'Failed to save retention settings');
- }
- } catch (error) {
- console.error('Error saving group retention settings:', error);
- if (statusSpan) {
- statusSpan.innerHTML = ` Error: ${error.message}`;
- }
- showToast(`Error saving retention settings: ${error.message}`, 'danger');
- }
- }
/* ===================== GROUP DOCUMENTS ===================== */
diff --git a/application/single_app/templates/manage_group.html b/application/single_app/templates/manage_group.html
index 78f80cdb..e99d9f4c 100644
--- a/application/single_app/templates/manage_group.html
+++ b/application/single_app/templates/manage_group.html
@@ -222,6 +222,14 @@
Loading...
Stats
+ {% if app_settings.enable_retention_policy_group %}
+
+
+
+ {% endif %}
@@ -471,6 +479,73 @@
Activity Timeline
+
+ {% if app_settings.enable_retention_policy_group %}
+
+
+
+
Retention Policy Settings
+
Configure how long to keep conversations and documents in this group workspace. Items older than the specified period will be automatically deleted.
+
+
+
+ Default: You can use the organization default or set a custom retention period. Choose "No automatic deletion" to keep items indefinitely.
+
+
+
+
+
+
+ Conversations older than this will be automatically deleted.
+
+
+
+
+
+ Documents older than this will be automatically deleted.
+
+
+
+
+
+
+
+
+
+
+ Important: Deleted conversations will be archived if archiving is enabled. All deletions are logged in activity history.
+
- {% if app_settings.enable_retention_policy_public %}
-
-
-
- {% endif %}
@@ -382,73 +377,6 @@
items per page
-
- {% if app_settings.enable_retention_policy_public %}
-
-
-
-
Retention Policy Settings
-
Configure how long to keep conversations and documents in this public workspace. Items older than the specified period will be automatically deleted.
-
-
-
- Default: You can use the organization default or set a custom retention period. Choose "No automatic deletion" to keep items indefinitely.
-
-
-
-
-
-
- Conversations older than this will be automatically deleted.
-
-
-
-
-
- Documents older than this will be automatically deleted.
-
-
-
-
-
-
-
-
-
-
- Important: Deleted conversations will be archived if archiving is enabled. All deletions are logged in activity history.
-
-
-
- {% endif %}
diff --git a/docs/explanation/features/CONVERSATION_EXPORT.md b/docs/explanation/features/v0.239.001/CONVERSATION_EXPORT.md
similarity index 100%
rename from docs/explanation/features/CONVERSATION_EXPORT.md
rename to docs/explanation/features/v0.239.001/CONVERSATION_EXPORT.md
diff --git a/docs/explanation/fixes/AGENT_SCHEMA_REF_RESOLUTION_FIX.md b/docs/explanation/fixes/v0.239.001/AGENT_SCHEMA_REF_RESOLUTION_FIX.md
similarity index 100%
rename from docs/explanation/fixes/AGENT_SCHEMA_REF_RESOLUTION_FIX.md
rename to docs/explanation/fixes/v0.239.001/AGENT_SCHEMA_REF_RESOLUTION_FIX.md
diff --git a/docs/explanation/fixes/CHATS_USER_SETTINGS_HARDENING_FIX.md b/docs/explanation/fixes/v0.239.001/CHATS_USER_SETTINGS_HARDENING_FIX.md
similarity index 100%
rename from docs/explanation/fixes/CHATS_USER_SETTINGS_HARDENING_FIX.md
rename to docs/explanation/fixes/v0.239.001/CHATS_USER_SETTINGS_HARDENING_FIX.md
diff --git a/docs/explanation/fixes/TAG_FILTER_INJECTION_FIX.md b/docs/explanation/fixes/v0.239.001/TAG_FILTER_INJECTION_FIX.md
similarity index 100%
rename from docs/explanation/fixes/TAG_FILTER_INJECTION_FIX.md
rename to docs/explanation/fixes/v0.239.001/TAG_FILTER_INJECTION_FIX.md
diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md
index 9bee2f42..3292f72a 100644
--- a/docs/explanation/release_notes.md
+++ b/docs/explanation/release_notes.md
@@ -2,37 +2,21 @@
# Feature Release
-### **(v0.238.025)**
-
-#### Bug Fixes
-
-* **Public Workspace setActive 403 Fix**
- * Fixed issue where non-owner/admin/document-manager users received a 403 "Not a member" error when trying to activate a public workspace for chat.
- * Root cause was an overly restrictive membership check on the `/api/public_workspaces/setActive` endpoint that only allowed owners, admins, and document managers — even though public workspaces are intended to be accessible to all authenticated users for chatting.
- * Removed the membership verification from the `setActive` endpoint; the route still requires authentication (`@login_required`, `@user_required`) and the public workspaces feature flag (`@enabled_required`).
- * Other admin-level endpoints (listing members, viewing stats, ownership transfer) retain their membership checks.
- * (Ref: `route_backend_public_workspaces.py`, `api_set_active_public_workspace`)
-* **Chats Page User Settings Hardening**
- * Fixed a user-specific chats page failure where only one affected user could not load `/chats` due to malformed per-user settings data.
- * **Root Cause**: The chats route assumed `user_settings["settings"]` was always a dictionary. If that field existed but had an invalid type (for example string, null, or list), the page could fail before rendering.
- * **Solution**: Hardened `get_user_settings()` to normalize missing/malformed `settings` to `{}` and persist the repaired document. Hardened the chats route to use safe dictionary fallbacks when reading nested settings values.
- * **Telemetry**: Added repair logging (`[UserSettings] Malformed settings repaired`) to improve diagnostics for future user-specific data-shape issues.
- * **Files Modified**: `functions_settings.py`, `route_frontend_chats.py`, `config.py`.
- * **Files Added**: `test_chats_user_settings_hardening_fix.py`, `CHATS_USER_SETTINGS_HARDENING_FIX.md`.
- * (Ref: user settings normalization, `/chats` route resilience, `functional_tests/test_chats_user_settings_hardening_fix.py`, `docs/explanation/fixes/CHATS_USER_SETTINGS_HARDENING_FIX.md`)
-* **Tag Filter Input Sanitization (Injection Prevention)**
- * Added `sanitize_tags_for_filter()` function to validate tag filter inputs against the same `^[a-z0-9_-]+$` character whitelist enforced when saving tags.
- * Previously, tag filter values from query parameters only passed through `normalize_tag()` (strip + lowercase) without character validation, allowing arbitrary characters to reach OData filter construction in `build_tags_filter()`.
- * Hardened `build_tags_filter()` in `functions_search.py` to validate tags before interpolating into OData expressions, eliminating the OData injection vector.
- * Updated tag filter parsing in personal, group, and public document routes to use `sanitize_tags_for_filter()` for defense-in-depth.
- * Invalid tag filter values are silently dropped (they cannot match any stored tag).
- * **Files Modified**: `functions_documents.py`, `functions_search.py`, `route_backend_documents.py`, `route_backend_group_documents.py`, `route_backend_public_documents.py`.
- * (Ref: `TAG_FILTER_INJECTION_FIX.md`, `sanitize_tags_for_filter`)
-
-### **(v0.238.024)**
+### **(v0.239.001)**
#### New Features
+* **Conversation Export**
+ * Export one or multiple conversations from the Chat page in JSON or Markdown format.
+ * **Single Export**: Use the ellipsis menu on any conversation to quickly export it.
+ * **Multi-Export**: Enter selection mode, check the conversations you want, and click the export button.
+ * A guided 4-step wizard walks you through selection review, format choice, packaging options (single file or ZIP archive), and download.
+ * Sensitive internal metadata is automatically stripped from exported data for security.
+
+* **Retention Policy UI for Groups and Public Workspaces**
+ * Can now configure conversation and document retention periods directly from the workspace and group management page.
+ * Choose from preset retention periods ranging from 7 days to 10 years, use the organization default, or disable automatic deletion entirely.
+
* **Owner-Only Group Agent and Action Management**
* New admin setting to restrict group agent and group action management (create, edit, delete) to only the group Owner role.
* **Admin Toggle**: "Require Owner to Manage Group Agents and Actions" located in Admin Settings > My Groups section, under the existing group creation membership setting.
@@ -108,19 +92,6 @@
* **Files Modified**: `chat-documents.js`, `chat-messages.js`, `functions_search.py`, `route_backend_chats.py`, `chats.html`.
* (Ref: Multi-document selection, tag filtering, OData search integration, `CHAT_DOCUMENT_AND_TAG_FILTERING.md`)
-#### New Features
-
-* **Conversation Export**
- * Export one or multiple conversations from the Chat page in JSON or Markdown format.
- * **Single Export**: Use the ellipsis menu on any conversation to quickly export it.
- * **Multi-Export**: Enter selection mode, check the conversations you want, and click the export button.
- * A guided 4-step wizard walks you through selection review, format choice, packaging options (single file or ZIP archive), and download.
- * Sensitive internal metadata is automatically stripped from exported data for security.
-
-* **Retention Policy UI for Groups and Public Workspaces**
- * Can now configure conversation and document retention periods directly from the workspace and group management page.
- * Choose from preset retention periods ranging from 7 days to 10 years, use the organization default, or disable automatic deletion entirely.
-
#### Bug Fixes
* **Citation Parsing Bug Fix**
@@ -130,6 +101,31 @@
* **Files Modified**: `chat-citations.js`.
* (Ref: Citation parsing, page range handling, `CITATION_IMPROVEMENTS.md`)
+* **Public Workspace setActive 403 Fix**
+ * Fixed issue where non-owner/admin/document-manager users received a 403 "Not a member" error when trying to activate a public workspace for chat.
+ * Root cause was an overly restrictive membership check on the `/api/public_workspaces/setActive` endpoint that only allowed owners, admins, and document managers — even though public workspaces are intended to be accessible to all authenticated users for chatting.
+ * Removed the membership verification from the `setActive` endpoint; the route still requires authentication (`@login_required`, `@user_required`) and the public workspaces feature flag (`@enabled_required`).
+ * Other admin-level endpoints (listing members, viewing stats, ownership transfer) retain their membership checks.
+ * (Ref: `route_backend_public_workspaces.py`, `api_set_active_public_workspace`)
+
+* **Chats Page User Settings Hardening**
+ * Fixed a user-specific chats page failure where only one affected user could not load `/chats` due to malformed per-user settings data.
+ * **Root Cause**: The chats route assumed `user_settings["settings"]` was always a dictionary. If that field existed but had an invalid type (for example string, null, or list), the page could fail before rendering.
+ * **Solution**: Hardened `get_user_settings()` to normalize missing/malformed `settings` to `{}` and persist the repaired document. Hardened the chats route to use safe dictionary fallbacks when reading nested settings values.
+ * **Telemetry**: Added repair logging (`[UserSettings] Malformed settings repaired`) to improve diagnostics for future user-specific data-shape issues.
+ * **Files Modified**: `functions_settings.py`, `route_frontend_chats.py`, `config.py`.
+ * **Files Added**: `test_chats_user_settings_hardening_fix.py`, `CHATS_USER_SETTINGS_HARDENING_FIX.md`.
+ * (Ref: user settings normalization, `/chats` route resilience, `functional_tests/test_chats_user_settings_hardening_fix.py`, `docs/explanation/fixes/CHATS_USER_SETTINGS_HARDENING_FIX.md`)
+
+* **Tag Filter Input Sanitization (Injection Prevention)**
+ * Added `sanitize_tags_for_filter()` function to validate tag filter inputs against the same `^[a-z0-9_-]+$` character whitelist enforced when saving tags.
+ * Previously, tag filter values from query parameters only passed through `normalize_tag()` (strip + lowercase) without character validation, allowing arbitrary characters to reach OData filter construction in `build_tags_filter()`.
+ * Hardened `build_tags_filter()` in `functions_search.py` to validate tags before interpolating into OData expressions, eliminating the OData injection vector.
+ * Updated tag filter parsing in personal, group, and public document routes to use `sanitize_tags_for_filter()` for defense-in-depth.
+ * Invalid tag filter values are silently dropped (they cannot match any stored tag).
+ * **Files Modified**: `functions_documents.py`, `functions_search.py`, `route_backend_documents.py`, `route_backend_group_documents.py`, `route_backend_public_documents.py`.
+ * (Ref: `TAG_FILTER_INJECTION_FIX.md`, `sanitize_tags_for_filter`)
+
#### User Interface Enhancements
* **Extended Document Dropdown Width**