collavre 0.24.0 → 0.25.0
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.
- checksums.yaml +4 -4
- data/README.md +0 -1
- data/app/assets/stylesheets/collavre/comments_popup.css +165 -99
- data/app/assets/stylesheets/collavre/creatives.css +346 -37
- data/app/assets/stylesheets/collavre/desktop_setup.css +246 -0
- data/app/assets/stylesheets/collavre/gnb.css +64 -3
- data/app/assets/stylesheets/collavre/landing.css +108 -3
- data/app/assets/stylesheets/collavre/mention_menu.css +32 -0
- data/app/assets/stylesheets/collavre/popup.css +14 -0
- data/app/assets/stylesheets/collavre/print.css +0 -1
- data/app/assets/stylesheets/collavre/search_popup.css +1 -1
- data/app/assets/stylesheets/collavre/secret_fields.css +3 -0
- data/app/assets/stylesheets/collavre/workspace.css +307 -0
- data/app/channels/collavre/agent_channel.rb +14 -21
- data/app/channels/collavre/comments_presence_channel.rb +181 -21
- data/app/channels/collavre/inbox_badge_channel.rb +7 -1
- data/app/components/collavre/inbox/badge_component.rb +6 -13
- data/app/components/collavre/progress_filter_component.html.erb +2 -2
- data/app/components/collavre/progress_filter_component.rb +20 -0
- data/app/controllers/collavre/admin/settings_controller.rb +4 -1
- data/app/controllers/collavre/agent_connections_controller.rb +165 -0
- data/app/controllers/collavre/agent_gateways_controller.rb +132 -0
- data/app/controllers/collavre/agent_provisioning_controller.rb +79 -0
- data/app/controllers/collavre/api/v1/agents_controller.rb +45 -97
- data/app/controllers/collavre/application_controller.rb +13 -2
- data/app/controllers/collavre/comment_read_pointers_controller.rb +69 -40
- data/app/controllers/collavre/comments_controller.rb +45 -131
- data/app/controllers/collavre/concerns/shareable.rb +9 -3
- data/app/controllers/collavre/creatives_controller.rb +144 -11
- data/app/controllers/collavre/desktop_gateway_registrations_controller.rb +173 -0
- data/app/controllers/collavre/desktop_setup_controller.rb +119 -0
- data/app/controllers/collavre/desktop_sidecar_health_controller.rb +35 -0
- data/app/controllers/collavre/features_controller.rb +57 -0
- data/app/controllers/collavre/invites_controller.rb +12 -1
- data/app/controllers/collavre/llm_models_controller.rb +8 -0
- data/app/controllers/collavre/notices_controller.rb +20 -0
- data/app/controllers/collavre/tasks_controller.rb +40 -6
- data/app/controllers/collavre/topics_controller.rb +130 -53
- data/app/controllers/collavre/user_creative_preferences_controller.rb +214 -30
- data/app/controllers/concerns/collavre/comments/batch_operations.rb +15 -3
- data/app/controllers/concerns/collavre/comments/conversion.rb +1 -1
- data/app/controllers/concerns/collavre/users_controller/ai_user_management.rb +118 -8
- data/app/controllers/concerns/collavre/users_controller/profile_and_settings.rb +1 -15
- data/app/controllers/concerns/collavre/users_controller/registration.rb +2 -2
- data/app/errors/collavre/turn_deadline_error.rb +20 -0
- data/app/helpers/collavre/application_helper.rb +38 -0
- data/app/helpers/collavre/creatives_helper.rb +56 -22
- data/app/helpers/collavre/navigation_helper.rb +12 -0
- data/app/javascript/collavre.js +1 -2
- data/app/javascript/components/InlineLexicalEditor.jsx +30 -21
- data/app/javascript/components/__tests__/creative_tree_row_parent_id.test.js +32 -2
- data/app/javascript/components/__tests__/creative_tree_row_progress_toggle.test.js +185 -0
- data/app/javascript/components/creative_tree_row.js +62 -22
- data/app/javascript/controllers/__tests__/agent_connection_controller.test.js +195 -0
- data/app/javascript/controllers/__tests__/desktop_proxy_setup_controller.test.js +96 -0
- data/app/javascript/controllers/__tests__/index.test.js +42 -0
- data/app/javascript/controllers/__tests__/last_visited_creative_controller.test.js +103 -0
- data/app/javascript/controllers/__tests__/link_creative_controller.test.js +253 -2
- data/app/javascript/controllers/__tests__/progress_filter_controller.test.js +134 -0
- data/app/javascript/controllers/__tests__/search_popup_controller.test.js +310 -0
- data/app/javascript/controllers/__tests__/topic_list_controller.test.js +111 -0
- data/app/javascript/controllers/__tests__/workspace_tree_controller.test.js +1011 -0
- data/app/javascript/controllers/agent_connection_controller.js +357 -0
- data/app/javascript/controllers/agent_vendor_controller.js +23 -0
- data/app/javascript/controllers/comment_controller.js +3 -1
- data/app/javascript/controllers/comments/__tests__/feature_cards_controller.test.js +223 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_default_placeholder.test.js +83 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_draft.test.js +2069 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_ime_send.test.js +324 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_review.test.js +28 -2
- data/app/javascript/controllers/comments/__tests__/form_controller_search_submit.test.js +106 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_stashed_draft.test.js +134 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_stashed_draft_creative_switch.test.js +166 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_stashed_draft_failure.test.js +183 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_deep_link_topic.test.js +467 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_docked.test.js +115 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_prev_message.test.js +245 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_read_pointer.test.js +276 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_stream_archived.test.js +221 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_stream_search.test.js +104 -0
- data/app/javascript/controllers/comments/__tests__/placeholder_controller.test.js +79 -0
- data/app/javascript/controllers/comments/__tests__/popup_controller.test.js +754 -1
- data/app/javascript/controllers/comments/__tests__/presence_controller.test.js +717 -15
- data/app/javascript/controllers/comments/__tests__/prev_message_navigator.test.js +129 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_action_errors.test.js +184 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_archived.test.js +879 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_creation_container.test.js +154 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_creation_scroll.test.js +92 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_delete.test.js +130 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_docked.test.js +32 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_last_topic_echo.test.js +1938 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_primary_agent.test.js +256 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_scroll.test.js +182 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_selection_race.test.js +981 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_topic_list.test.js +313 -0
- data/app/javascript/controllers/comments/feature_cards_controller.js +83 -0
- data/app/javascript/controllers/comments/form_controller.js +795 -14
- data/app/javascript/controllers/comments/list_controller.js +338 -52
- data/app/javascript/controllers/comments/placeholder_controller.js +30 -0
- data/app/javascript/controllers/comments/popup_controller.js +425 -83
- data/app/javascript/controllers/comments/presence_controller.js +317 -55
- data/app/javascript/controllers/comments/prev_message_navigator.js +94 -0
- data/app/javascript/controllers/comments/topics_controller.js +1772 -100
- data/app/javascript/controllers/common_popup_controller.js +2 -2
- data/app/javascript/controllers/creatives/__tests__/select_mode_controller.test.js +139 -0
- data/app/javascript/controllers/creatives/__tests__/tree_controller.test.js +605 -0
- data/app/javascript/controllers/creatives/__tests__/write_access_request_controller.test.js +125 -0
- data/app/javascript/controllers/creatives/select_mode_controller.js +7 -2
- data/app/javascript/controllers/creatives/tree_controller.js +140 -31
- data/app/javascript/controllers/creatives/write_access_request_controller.js +58 -0
- data/app/javascript/controllers/desktop_proxy_setup_controller.js +51 -0
- data/app/javascript/controllers/gateway_check_controller.js +36 -0
- data/app/javascript/controllers/index.js +25 -1
- data/app/javascript/controllers/last_visited_creative_controller.js +58 -0
- data/app/javascript/controllers/link_creative_controller.js +89 -18
- data/app/javascript/controllers/progress_filter_controller.js +12 -22
- data/app/javascript/controllers/search_popup_controller.js +64 -46
- data/app/javascript/controllers/share_user_search_controller.js +2 -2
- data/app/javascript/controllers/topic_list_controller.js +44 -7
- data/app/javascript/controllers/workspace_tree_controller.js +662 -0
- data/app/javascript/creatives/__tests__/tree_renderer.test.js +80 -0
- data/app/javascript/creatives/drag_drop/__tests__/drop_completion_empty_state.test.js +98 -0
- data/app/javascript/creatives/drag_drop/__tests__/topic_move_released_agent.test.js +125 -0
- data/app/javascript/creatives/drag_drop/event_handlers.js +12 -0
- data/app/javascript/creatives/tree_renderer.js +65 -22
- data/app/javascript/lib/__tests__/chat_drafts.test.js +1458 -0
- data/app/javascript/lib/__tests__/common_popup_bounds.test.js +8 -3
- data/app/javascript/lib/__tests__/common_popup_placement.test.js +374 -0
- data/app/javascript/lib/__tests__/common_popup_reshow.test.js +131 -0
- data/app/javascript/lib/__tests__/creative_link_prefetch.test.js +46 -0
- data/app/javascript/lib/__tests__/last_visited_creative.test.js +87 -0
- data/app/javascript/lib/__tests__/skill_html_entities.test.js +23 -0
- data/app/javascript/lib/__tests__/turbo_stream_actions.test.js +190 -0
- data/app/javascript/lib/api/__tests__/creatives.test.js +68 -0
- data/app/javascript/lib/api/__tests__/csrf_fetch.test.js +27 -0
- data/app/javascript/lib/api/__tests__/topics_save_last_topic.test.js +112 -0
- data/app/javascript/lib/api/creatives.js +32 -13
- data/app/javascript/lib/api/csrf_fetch.js +6 -4
- data/app/javascript/lib/api/topics.js +63 -5
- data/app/javascript/lib/chat_drafts.js +786 -0
- data/app/javascript/lib/common_popup.js +164 -25
- data/app/javascript/lib/creative_link_prefetch.js +27 -0
- data/app/javascript/lib/last_visited_creative.js +97 -0
- data/app/javascript/lib/lexical/__tests__/creative_link_node.test.js +162 -0
- data/app/javascript/lib/lexical/__tests__/creative_link_trigger.test.js +133 -0
- data/app/javascript/lib/lexical/__tests__/link_toolbar.test.js +62 -0
- data/app/javascript/lib/lexical/creative_link_node.js +101 -0
- data/app/javascript/lib/lexical/creative_link_trigger.js +98 -0
- data/app/javascript/lib/lexical/link_toolbar.js +23 -0
- data/app/javascript/lib/turbo_stream_actions.js +23 -16
- data/app/javascript/lib/utils/__tests__/filter_navigation.test.js +210 -0
- data/app/javascript/lib/utils/__tests__/markdown.test.js +44 -0
- data/app/javascript/lib/utils/filter_navigation.js +100 -0
- data/app/javascript/modules/__tests__/command_menu_creative_picker.test.js +62 -0
- data/app/javascript/modules/__tests__/command_menu_typeahead.test.js +267 -0
- data/app/javascript/modules/__tests__/creative_link_picker.test.js +85 -0
- data/app/javascript/modules/__tests__/creative_row_editor_archive.test.js +391 -0
- data/app/javascript/modules/__tests__/creative_row_editor_delegated_clicks.test.js +158 -0
- data/app/javascript/modules/__tests__/creative_row_editor_empty_state_recovery.test.js +519 -0
- data/app/javascript/modules/__tests__/creative_row_editor_empty_state_removal.test.js +346 -0
- data/app/javascript/modules/__tests__/creative_row_editor_session.test.js +144 -0
- data/app/javascript/modules/__tests__/creative_tree_dom.test.js +30 -0
- data/app/javascript/modules/__tests__/creative_tree_empty_state.test.js +186 -0
- data/app/javascript/modules/__tests__/dom_listener_registry.test.js +67 -0
- data/app/javascript/modules/__tests__/support/inline_editor_dom.js +159 -0
- data/app/javascript/modules/command_args_form.js +0 -12
- data/app/javascript/modules/command_menu.js +80 -38
- data/app/javascript/modules/creative_link_picker.js +56 -0
- data/app/javascript/modules/creative_row_editor.js +446 -243
- data/app/javascript/modules/creative_row_editor_delegated_clicks.js +87 -0
- data/app/javascript/modules/creative_tree_dom.js +4 -0
- data/app/javascript/modules/creative_tree_empty_state.js +80 -0
- data/app/javascript/modules/dom_listener_registry.js +23 -0
- data/app/javascript/modules/mention_menu.js +2 -3
- data/app/javascript/utils/__tests__/caret_anchor.test.js +75 -0
- data/app/javascript/utils/__tests__/chevron_icons.test.js +15 -0
- data/app/javascript/utils/caret_position.js +10 -0
- data/app/javascript/utils/chevron_icons.js +7 -0
- data/app/jobs/collavre/ai_agent_job.rb +262 -72
- data/app/jobs/collavre/cancel_offline_delegated_tasks_job.rb +11 -36
- data/app/jobs/collavre/comment_badges_broadcast_job.rb +28 -0
- data/app/jobs/collavre/comment_link_preview_job.rb +33 -0
- data/app/jobs/collavre/comment_notification_job.rb +14 -0
- data/app/jobs/collavre/comment_push_delivery_sweep_job.rb +17 -0
- data/app/jobs/collavre/compress_job.rb +33 -22
- data/app/jobs/collavre/creative_broadcast_job.rb +161 -48
- data/app/jobs/collavre/cron_action_job.rb +36 -18
- data/app/jobs/collavre/drop_trigger_job.rb +23 -9
- data/app/jobs/collavre/merge_comments_job.rb +33 -16
- data/app/jobs/collavre/push_notification_job.rb +62 -18
- data/app/jobs/collavre/restore_dropped_dispatches_job.rb +17 -0
- data/app/jobs/collavre/stuck_detector_job.rb +1 -1
- data/app/jobs/collavre/touch_creative_subtree_job.rb +14 -0
- data/app/models/collavre/agent_gateway.rb +200 -0
- data/app/models/collavre/agent_subscription.rb +4 -4
- data/app/models/collavre/agent_workspace.rb +210 -0
- data/app/models/collavre/comment/broadcastable.rb +22 -52
- data/app/models/collavre/comment/notifiable.rb +154 -77
- data/app/models/collavre/comment/topic_membership.rb +41 -0
- data/app/models/collavre/comment.rb +336 -31
- data/app/models/collavre/comment_notification_delivery.rb +61 -0
- data/app/models/collavre/comment_presence_store/cache_batches.rb +64 -0
- data/app/models/collavre/comment_presence_store.rb +272 -12
- data/app/models/collavre/comment_read_pointer.rb +9 -15
- data/app/models/collavre/creative/describable.rb +8 -2
- data/app/models/collavre/creative/permissible.rb +11 -0
- data/app/models/collavre/creative.rb +29 -4
- data/app/models/collavre/creative_share.rb +109 -4
- data/app/models/collavre/current.rb +1 -0
- data/app/models/collavre/llm_model.rb +62 -0
- data/app/models/collavre/system_setting.rb +12 -1
- data/app/models/collavre/task.rb +121 -2
- data/app/models/collavre/topic.rb +51 -0
- data/app/models/collavre/user.rb +101 -41
- data/app/models/collavre/user_creative_preference.rb +3 -1
- data/app/services/collavre/agent_provisioning/archive.rb +108 -0
- data/app/services/collavre/ai_agent/a2a_dispatcher.rb +16 -3
- data/app/services/collavre/ai_agent/agent_lifecycle_manager.rb +63 -6
- data/app/services/collavre/ai_agent/approval_handler.rb +12 -4
- data/app/services/collavre/ai_agent/claude_channel_adapter.rb +10 -5
- data/app/services/collavre/ai_agent/merged_trigger_comments.rb +164 -0
- data/app/services/collavre/ai_agent/message_builder.rb +103 -16
- data/app/services/collavre/ai_agent/session_provisioner.rb +3 -7
- data/app/services/collavre/ai_agent/task_claim_service.rb +29 -15
- data/app/services/collavre/ai_agent/task_reply_service.rb +67 -0
- data/app/services/collavre/ai_agent_service.rb +108 -20
- data/app/services/collavre/ai_client.rb +211 -21
- data/app/services/collavre/cli_proxy/client.rb +147 -0
- data/app/services/collavre/cli_proxy/endpoint_policy.rb +81 -0
- data/app/services/collavre/cli_proxy/identity.rb +47 -0
- data/app/services/collavre/cli_proxy/safe_net_http_adapter.rb +19 -0
- data/app/services/collavre/command_menu_service.rb +0 -10
- data/app/services/collavre/comment_move_service.rb +96 -7
- data/app/services/collavre/comment_snapshot_restore_service.rb +11 -5
- data/app/services/collavre/comments/action_executor.rb +48 -20
- data/app/services/collavre/comments/command_processor.rb +0 -1
- data/app/services/collavre/comments/list_response.rb +133 -0
- data/app/services/collavre/comments/read_pointer_writer.rb +210 -0
- data/app/services/collavre/comments/read_receipt_broadcaster.rb +157 -0
- data/app/services/collavre/comments/read_receipt_index.rb +152 -0
- data/app/services/collavre/comments/topic_command.rb +72 -1
- data/app/services/collavre/comments/topic_mutation.rb +20 -0
- data/app/services/collavre/creatives/breadcrumb_resolver.rb +1 -1
- data/app/services/collavre/creatives/comment_badge_index.rb +299 -27
- data/app/services/collavre/creatives/reorderer.rb +15 -3
- data/app/services/collavre/creatives/tree_builder.rb +1 -1
- data/app/services/collavre/creatives/tree_formatter.rb +5 -6
- data/app/services/collavre/creatives/workspace_path_resolver.rb +50 -0
- data/app/services/collavre/creatives/workspace_tree_builder.rb +92 -0
- data/app/services/collavre/crons/recurring_topic_tasks.rb +23 -0
- data/app/services/collavre/home_page_path.rb +55 -0
- data/app/services/collavre/html_text.rb +64 -0
- data/app/services/collavre/http_client.rb +11 -2
- data/app/services/collavre/inbox_reply_service.rb +1 -1
- data/app/services/collavre/markdown_converter.rb +13 -1
- data/app/services/collavre/orchestration/agent_orchestrator.rb +776 -81
- data/app/services/collavre/orchestration/arbiter.rb +48 -5
- data/app/services/collavre/orchestration/deferred_trigger_scope.rb +53 -0
- data/app/services/collavre/orchestration/delivery_record.rb +792 -0
- data/app/services/collavre/orchestration/loop_breaker.rb +10 -4
- data/app/services/collavre/orchestration/matcher.rb +132 -17
- data/app/services/collavre/orchestration/policy_resolver.rb +58 -10
- data/app/services/collavre/orchestration/scheduler.rb +10 -4
- data/app/services/collavre/orchestration/scheduling_hooks.rb +10 -0
- data/app/services/collavre/orchestration/selection.rb +43 -0
- data/app/services/collavre/orchestration/stuck_detector.rb +25 -112
- data/app/services/collavre/orchestration/task_coalescer.rb +355 -0
- data/app/services/collavre/orchestration/topic_slot.rb +73 -0
- data/app/services/collavre/system_events/context_builder.rb +60 -11
- data/app/services/collavre/system_events/dispatcher.rb +58 -12
- data/app/services/collavre/system_events/envelope.rb +73 -0
- data/app/services/collavre/system_events/vocabulary.rb +45 -0
- data/app/services/collavre/tools/creative_retrieval_service.rb +1 -1
- data/app/services/collavre/tools/cron_create_service.rb +32 -18
- data/app/services/collavre/tools/id_list.rb +38 -0
- data/app/services/collavre/tools/topic_authorizer.rb +61 -7
- data/app/services/collavre/tools/topic_branch_service.rb +85 -0
- data/app/services/collavre/tools/topic_create_service.rb +117 -0
- data/app/services/collavre/tools/topic_list_service.rb +101 -0
- data/app/services/collavre/tools/topic_message_create_service.rb +192 -0
- data/app/services/collavre/tools/topic_messages_service.rb +346 -0
- data/app/services/collavre/tools/topic_move_service.rb +108 -0
- data/app/services/collavre/tools/topic_selection.rb +59 -0
- data/app/services/collavre/tools/topic_update_service.rb +165 -0
- data/app/services/collavre/topic_branch_service.rb +39 -10
- data/app/services/collavre/topics/agent_resolver.rb +87 -0
- data/app/services/collavre/topics/char_budget.rb +70 -0
- data/app/services/collavre/topics/last_topic_preference_payload.rb +18 -0
- data/app/services/collavre/topics/message_page.rb +380 -0
- data/app/services/collavre/topics/message_page_markdown.rb +102 -0
- data/app/services/collavre/topics/message_scope.rb +61 -0
- data/app/services/collavre/topics/message_stats.rb +92 -0
- data/app/services/collavre/topics/move_blocker.rb +47 -0
- data/app/services/collavre/topics/next_name.rb +35 -0
- data/app/services/collavre/topics/pin_rejection.rb +25 -0
- data/app/services/collavre/topics/read_pointer_relocator.rb +71 -0
- data/app/services/collavre/topics/reserved_name.rb +18 -0
- data/app/services/collavre/topics/serializer.rb +92 -0
- data/app/services/collavre/topics/topic_destroy.rb +30 -0
- data/app/services/collavre/topics/topic_move.rb +80 -0
- data/app/services/collavre/topics/topic_move_effects.rb +35 -0
- data/app/services/collavre/topics/topic_mutation.rb +27 -0
- data/app/views/collavre/admin/integrations/_setting_row.html.erb +5 -8
- data/app/views/collavre/admin/settings/_system_tab.html.erb +7 -0
- data/app/views/collavre/agent_connections/show.html.erb +77 -0
- data/app/views/collavre/agent_gateways/_form.html.erb +71 -0
- data/app/views/collavre/agent_gateways/edit.html.erb +2 -0
- data/app/views/collavre/agent_gateways/index.html.erb +54 -0
- data/app/views/collavre/agent_gateways/new.html.erb +2 -0
- data/app/views/collavre/comments/_comments_popup.html.erb +32 -17
- data/app/views/collavre/comments/_empty_state.html.erb +33 -0
- data/app/views/collavre/comments/_empty_state_minimal.html.erb +7 -0
- data/app/views/collavre/comments/_feature_card.html.erb +35 -0
- data/app/views/collavre/comments/_list.html.erb +3 -2
- data/app/views/collavre/comments/_no_search_results.html.erb +3 -0
- data/app/views/collavre/creatives/_add_button.html.erb +17 -19
- data/app/views/collavre/creatives/_empty_state.html.erb +109 -0
- data/app/views/collavre/creatives/_inline_edit_form.html.erb +5 -5
- data/app/views/collavre/creatives/_tree_loading_placeholder.html.erb +16 -0
- data/app/views/collavre/creatives/_workspace_tree.html.erb +33 -0
- data/app/views/collavre/creatives/index.html.erb +79 -18
- data/app/views/collavre/desktop_setup/show.html.erb +114 -0
- data/app/views/collavre/features/index.html.erb +42 -0
- data/app/views/collavre/features/show.html.erb +52 -0
- data/app/views/collavre/invitation_mailer/invite.html.erb +1 -1
- data/app/views/collavre/invitation_mailer/invite.text.erb +1 -1
- data/app/views/collavre/invites/show.html.erb +14 -1
- data/app/views/collavre/landing/show.html.erb +5 -1
- data/app/views/collavre/shared/_link_creative_modal.html.erb +4 -1
- data/app/views/collavre/shared/navigation/_help_button.html.erb +3 -1
- data/app/views/collavre/shared/navigation/_panels.html.erb +1 -8
- data/app/views/collavre/shared/navigation/_search_form.html.erb +13 -2
- data/app/views/collavre/user_themes/index.html.erb +1 -1
- data/app/views/collavre/users/_llm_fields.html.erb +65 -0
- data/app/views/collavre/users/_org_chart_node.html.erb +1 -1
- data/app/views/collavre/users/edit_ai.html.erb +33 -31
- data/app/views/collavre/users/new.html.erb +4 -2
- data/app/views/collavre/users/new_ai.html.erb +14 -32
- data/app/views/collavre/users/show.html.erb +13 -1
- data/app/views/layouts/collavre/landing.html.erb +9 -4
- data/config/initializers/ruby_llm_openai_reasoning_content.rb +77 -0
- data/config/locales/admin.en.yml +3 -1
- data/config/locales/admin.ko.yml +3 -1
- data/config/locales/agent_gateway.en.yml +217 -0
- data/config/locales/agent_gateway.ko.yml +217 -0
- data/config/locales/ai_agent.en.yml +0 -1
- data/config/locales/ai_agent.ko.yml +0 -1
- data/config/locales/comments.en.yml +72 -30
- data/config/locales/comments.ko.yml +72 -30
- data/config/locales/creatives.en.yml +32 -1
- data/config/locales/creatives.ko.yml +32 -1
- data/config/locales/features.en.yml +140 -0
- data/config/locales/features.ko.yml +140 -0
- data/config/locales/invites.en.yml +3 -0
- data/config/locales/invites.ko.yml +3 -0
- data/config/locales/landing.en.yml +2 -1
- data/config/locales/landing.ko.yml +2 -1
- data/config/locales/tools.en.yml +20 -0
- data/config/locales/tools.ko.yml +20 -0
- data/config/locales/users.en.yml +15 -14
- data/config/locales/users.ko.yml +14 -14
- data/config/routes.rb +45 -19
- data/db/migrate/20250911084338_backfill_creative_in_comment_inbox_items.rb +9 -1
- data/db/migrate/20260724000000_add_creative_id_index_to_comments.rb +34 -0
- data/db/migrate/20260724000001_add_creative_private_id_index_to_comments.rb +50 -0
- data/db/migrate/20260724090000_add_async_comment_notification_state.rb +64 -0
- data/db/migrate/20260727000000_add_waiting_notice_scope_to_comments.rb +30 -0
- data/db/migrate/20260727010000_add_waiting_notice_scope_to_tasks.rb +26 -0
- data/db/migrate/20260805000000_add_creative_workspace_enabled_to_users.rb +5 -0
- data/db/migrate/20260807050000_add_dismissed_notices_to_users.rb +5 -0
- data/db/migrate/20260809000000_change_creative_workspace_enabled_default_to_true.rb +18 -0
- data/db/migrate/20260809000001_create_llm_models.rb +64 -0
- data/db/migrate/20260809000002_create_agent_gateways_and_workspaces.rb +35 -0
- data/db/migrate/20260809000003_hash_agent_workspace_callback_tokens.rb +44 -0
- data/db/migrate/20260809000004_encrypt_agent_workspace_manifest_tokens.rb +59 -0
- data/db/migrate/20260810000000_split_agent_workspace_identity_axes.rb +58 -0
- data/db/migrate/20260810000001_seed_codex_custom_proxy_llm_models.rb +28 -0
- data/db/migrate/20260810010000_add_last_visited_creative_to_users.rb +8 -0
- data/db/migrate/20260810020000_add_last_visited_creative_at_to_users.rb +5 -0
- data/db/migrate/20260810030000_add_last_visited_creative_navigation_order_to_users.rb +6 -0
- data/db/migrate/20260810060000_add_last_visited_creative_issued_sequence_to_users.rb +5 -0
- data/db/migrate/20260810100000_add_desktop_managed_to_agent_gateways.rb +40 -0
- data/db/migrate/20260810100001_add_desktop_preset_adapter_to_users.rb +38 -0
- data/db/migrate/20260811000000_make_agent_gateway_completion_key_nullable.rb +7 -0
- data/db/migrate/20260812000000_remove_workflow_columns_from_tasks.rb +101 -0
- data/db/migrate/20260812000001_remove_android_devices.rb +24 -0
- data/db/migrate/20260812010000_drop_inbox_items.rb +22 -0
- data/db/migrate/20260813000000_clear_claude_channel_presence_routing_expressions.rb +16 -0
- data/db/migrate/20260813000001_add_last_topic_revision_to_user_creative_preferences.rb +5 -0
- data/db/migrate/20260813000002_add_last_topic_save_order_to_user_creative_preferences.rb +6 -0
- data/db/migrate/20260813000003_add_last_topic_save_sequences_to_user_creative_preferences.rb +5 -0
- data/db/migrate/20260813000004_add_last_topic_save_fences_to_user_creative_preferences.rb +6 -0
- data/db/migrate/20260814000000_add_topic_to_comment_read_pointers.rb +79 -0
- data/db/migrate/20260814000001_add_creative_topic_private_id_index_to_comments.rb +37 -0
- data/db/migrate/20260816000000_add_watermark_index_to_comment_read_pointers.rb +27 -0
- data/db/migrate/20260816000001_add_private_recipient_topic_index_to_comments.rb +48 -0
- data/db/migrate/20260820000000_add_topic_assigned_at_to_comments.rb +13 -0
- data/db/seeds.rb +1 -51
- data/lib/collavre/engine.rb +108 -1
- data/lib/collavre/feature_card_registry.rb +168 -0
- data/lib/collavre/hashed_access_token_lookup.rb +22 -0
- data/lib/collavre/sensitive_request_silencer.rb +23 -0
- data/lib/collavre/user_extensions.rb +0 -2
- data/lib/collavre/version.rb +1 -1
- data/lib/collavre.rb +1 -0
- data/skills/collavre/SKILL.md +165 -0
- data/skills/collavre/package.json +8 -0
- data/skills/collavre/references/tool-reference.md +347 -0
- data/skills/collavre/scripts/collavre +634 -0
- data/skills/collavre/scripts/html_entities.js +24 -0
- metadata +228 -30
- data/app/controllers/collavre/api/v1/mobile/agent_events_controller.rb +0 -224
- data/app/controllers/collavre/api/v1/mobile/base_controller.rb +0 -95
- data/app/controllers/collavre/api/v1/mobile/devices_controller.rb +0 -31
- data/app/controllers/collavre/api/v1/mobile/voice_commands_controller.rb +0 -25
- data/app/controllers/collavre/inbox_items_controller.rb +0 -70
- data/app/controllers/collavre/typo_corrections_controller.rb +0 -39
- data/app/javascript/lib/__tests__/typo_correction.test.js +0 -192
- data/app/javascript/lib/typo_correction.js +0 -146
- data/app/javascript/modules/__tests__/typo_corrector.test.js +0 -365
- data/app/javascript/modules/creative_guide.js +0 -67
- data/app/javascript/modules/typo_corrector.js +0 -534
- data/app/jobs/collavre/inbox_summary_job.rb +0 -24
- data/app/mailers/collavre/inbox_mailer.rb +0 -19
- data/app/models/collavre/inbox_item.rb +0 -89
- data/app/services/collavre/comments/concerns/workflow_support.rb +0 -115
- data/app/services/collavre/comments/work_command.rb +0 -161
- data/app/services/collavre/comments/workflow_executor.rb +0 -276
- data/app/services/collavre/gemini_parent_recommender.rb +0 -77
- data/app/services/collavre/mobile/event_summarizer.rb +0 -40
- data/app/services/collavre/typo_corrector.rb +0 -188
- data/app/views/collavre/inbox_items/_item.html.erb +0 -18
- data/app/views/collavre/inbox_items/_items.html.erb +0 -3
- data/app/views/collavre/inbox_items/_list.html.erb +0 -7
- data/app/views/collavre/inbox_items/index.html.erb +0 -1
- data/app/views/collavre/inbox_mailer/daily_summary.html.erb +0 -11
- data/app/views/collavre/inbox_mailer/daily_summary.text.erb +0 -8
- data/app/views/collavre/users/typo_correction.html.erb +0 -50
- data/config/locales/mobile.en.yml +0 -16
- data/config/locales/mobile.ko.yml +0 -16
data/config/locales/users.ko.yml
CHANGED
|
@@ -23,10 +23,16 @@ ko:
|
|
|
23
23
|
vendor_label: LLM 벤더
|
|
24
24
|
vendor_help: 이 에이전트에 사용할 AI 제공업체를 선택하세요
|
|
25
25
|
model_label: 모델
|
|
26
|
+
remove_model: 모델 목록에서 삭제
|
|
27
|
+
remove_model_failed: 모델을 목록에서 삭제하지 못했습니다.
|
|
26
28
|
api_key_label: API Key
|
|
27
29
|
api_key_help: 입력하면 시스템 기본값 대신 이 키가 사용됩니다. 암호화되어 저장됩니다.
|
|
28
30
|
gateway_url_label: Gateway URL
|
|
29
31
|
gateway_url_help: OpenClaw 사용 시 필수입니다. OpenClaw 게이트웨이의 URL을 입력하세요.
|
|
32
|
+
agent_gateway_label: Agent Gateway
|
|
33
|
+
agent_gateway_blank: CLI Proxy 게이트웨이를 선택하세요
|
|
34
|
+
agent_gateway_help: LLM 벤더가 CLI Proxy일 때만 필요합니다.
|
|
35
|
+
manage_gateways: 게이트웨이 관리
|
|
30
36
|
searchable_label: 이 AI를 검색 및 멘션 가능하게 허용
|
|
31
37
|
searchable_help: 활성화하면 다른 사용자가 이름으로 이 AI를 찾고 멘션할 수 있습니다.
|
|
32
38
|
submit: AI 에이전트 추가
|
|
@@ -51,7 +57,12 @@ ko:
|
|
|
51
57
|
not_an_ai: 이 사용자는 AI 에이전트가 아닙니다.
|
|
52
58
|
tools_title: 사용 가능한 도구
|
|
53
59
|
tools_description: 에이전트가 사용할 수 있는 도구 목록입니다.
|
|
60
|
+
meta_skills_title: Collavre 메타 스킬
|
|
61
|
+
meta_skills_description: 프로비저닝되는 collavre-cli 스킬 하나가 선택된 도구를 메타 스킬로 실행합니다. 현재는 이 선택을 callback token 권한으로 강제하지 않습니다.
|
|
62
|
+
gateway_mode: "워크스페이스 모드: %{mode}. 선택한 게이트웨이 설정에서 변경할 수 있습니다."
|
|
54
63
|
no_tools_found: 사용 가능한 도구가 없습니다.
|
|
64
|
+
clear_api_key_label: 저장된 API 키 삭제
|
|
65
|
+
clear_api_key_help: 저장 후 시스템 기본 키 또는 키가 필요 없는 게이트웨이를 사용합니다.
|
|
55
66
|
agent_conf_label: "에이전트 설정 (YAML)"
|
|
56
67
|
agent_conf_help: "에이전트 동작을 위한 YAML 설정. chat_history: 최대 이전 메시지 수 (기본 50), chat_history_size: 최대 총 문자 수 (기본 100000), context.creative_children_level: 크리에이티브 컨텍스트에 포함할 자식 깊이 (기본 nil → 사용자 설정 따름, 0=자식 없음, 1=직접 자식, 2=손자까지)."
|
|
57
68
|
chat_title: 에이전트와 대화하기
|
|
@@ -111,25 +122,14 @@ ko:
|
|
|
111
122
|
theme: 테마
|
|
112
123
|
calendar_id: 캘린더 ID
|
|
113
124
|
notifications_enabled: 알림 사용
|
|
125
|
+
creative_workspace:
|
|
126
|
+
enabled: 3단 크리에이티브 작업 공간 사용
|
|
127
|
+
description: 왼쪽 크리에이티브 트리를 유지하고 전체 페이지 새로고침 없이 중앙 내용을 전환하며 오른쪽에 채팅을 표시합니다.
|
|
114
128
|
locale: 언어
|
|
115
129
|
locales:
|
|
116
130
|
en: 영어
|
|
117
131
|
ko: 한국어
|
|
118
132
|
timezone: 시간대
|
|
119
|
-
typo_correction:
|
|
120
|
-
legend: 오타 자동 수정
|
|
121
|
-
manage: 오타 자동 수정 설정
|
|
122
|
-
description: 입력하는 동안 오타를 자동으로 제안하고 수정합니다. 아래의 타이핑 장치와 입력 위치가 모두 켜져 있을 때만 동작합니다.
|
|
123
|
-
enabled: 오타 자동 수정 사용
|
|
124
|
-
threshold: 자동 적용 기준
|
|
125
|
-
threshold_hint: 이 신뢰도(0-100) 이상이면 자동으로 적용되고, 낮으면 후보로만 표시됩니다.
|
|
126
|
-
device_legend: 타이핑 장치
|
|
127
|
-
on_voice: 음성 입력
|
|
128
|
-
on_soft_keyboard: 화면(소프트) 키보드
|
|
129
|
-
on_physical_keyboard: 물리 키보드(블루투스/유선)
|
|
130
|
-
location_legend: 입력 위치
|
|
131
|
-
in_chat: 채팅 메시지 입력
|
|
132
|
-
in_editor: 크리에이티브 편집기
|
|
133
133
|
profile_updated: 프로파일이 업데이트되었습니다.
|
|
134
134
|
avatar_updated: 아바타가 변경되었습니다.
|
|
135
135
|
password_updated: 비밀번호가 성공적으로 변경되었습니다.
|
data/config/routes.rb
CHANGED
|
@@ -2,9 +2,26 @@ Collavre::Engine.routes.draw do
|
|
|
2
2
|
# Landing page
|
|
3
3
|
get "landing", to: "landing#show"
|
|
4
4
|
|
|
5
|
+
# Public feature guides — the long-form counterpart to the empty-chat feature
|
|
6
|
+
# cards. Keys come from Collavre::FeatureCardRegistry, which rejects a key this
|
|
7
|
+
# constraint could not route, so the two cannot drift apart.
|
|
8
|
+
get "features", to: "features#index", as: :features
|
|
9
|
+
get "features/:key", to: "features#show", as: :feature,
|
|
10
|
+
constraints: { key: Collavre::FeatureCard::GUIDE_KEY_FORMAT }
|
|
11
|
+
|
|
5
12
|
# Authentication routes
|
|
6
13
|
resource :session, only: [ :new, :create, :destroy ]
|
|
7
14
|
resources :passwords, param: :token, only: [ :new, :create, :edit, :update ]
|
|
15
|
+
resources :agent_gateways, path: "settings/agent-gateways", except: :show do
|
|
16
|
+
post :check, on: :member
|
|
17
|
+
end
|
|
18
|
+
get "desktop/setup", to: "desktop_setup#show", as: :desktop_setup
|
|
19
|
+
post "desktop/setup/account", to: "desktop_setup#create_account", as: :desktop_setup_account
|
|
20
|
+
post "desktop/setup/registration-token", to: "desktop_setup#registration_token", as: :desktop_setup_registration_token
|
|
21
|
+
get "desktop/setup/sidecar-health", to: "desktop_sidecar_health#show", as: :desktop_setup_sidecar_health
|
|
22
|
+
post "desktop/setup/validate-registration-grant", to: "desktop_gateway_registrations#validate_registration_grant", as: :desktop_setup_validate_registration_grant
|
|
23
|
+
post "desktop/setup/gateway-registered", to: "desktop_gateway_registrations#registered", as: :desktop_setup_gateway_registered
|
|
24
|
+
post "desktop/setup/register-gateway", to: "desktop_gateway_registrations#create", as: :desktop_setup_register_gateway
|
|
8
25
|
resources :users, only: [ :index, :new, :create, :show, :edit, :update, :destroy ] do
|
|
9
26
|
collection do
|
|
10
27
|
get :new_ai
|
|
@@ -23,9 +40,27 @@ Collavre::Engine.routes.draw do
|
|
|
23
40
|
get :edit_password
|
|
24
41
|
patch :update_password
|
|
25
42
|
get :passkeys
|
|
26
|
-
get :
|
|
43
|
+
get :agent_connection, to: "agent_connections#show"
|
|
27
44
|
end
|
|
28
45
|
end
|
|
46
|
+
scope "users/:user_id/agent-connection", as: :agent_connection do
|
|
47
|
+
get :status, to: "agent_connections#status"
|
|
48
|
+
post "auth/:engine/sessions", to: "agent_connections#create_auth_session", as: :auth_sessions
|
|
49
|
+
get "auth/:engine/sessions/:session_id", to: "agent_connections#auth_session", as: :auth_session
|
|
50
|
+
post "auth/:engine/sessions/:session_id", to: "agent_connections#submit_auth_session"
|
|
51
|
+
delete "auth/:engine/sessions/:session_id", to: "agent_connections#cancel_auth_session"
|
|
52
|
+
post "provision/sync", to: "agent_connections#provision_sync", as: :provision_sync
|
|
53
|
+
post "provision/items/:type/:name/approve", to: "agent_connections#provision_approve", as: :provision_approve
|
|
54
|
+
delete "provision/items/:type/:name", to: "agent_connections#provision_delete", as: :provision_delete
|
|
55
|
+
post "rotate-tokens", to: "agent_connections#rotate_tokens", as: :rotate_tokens
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
get "/agents/:agent_id/workspaces/:token/provision.json",
|
|
59
|
+
to: "agent_provisioning#manifest", as: :agent_provision_manifest, format: false
|
|
60
|
+
get "/agent-provisioning/collavre/:sha256.tar.gz",
|
|
61
|
+
to: "agent_provisioning#skill", as: :agent_provision_skill, format: false
|
|
62
|
+
get "/agents/:agent_id/workspaces/:token/config/:sha256.tar.gz",
|
|
63
|
+
to: "agent_provisioning#config_archive", as: :agent_provision_config, format: false
|
|
29
64
|
get "/email_verification/:token", to: "email_verifications#show", as: :email_verification
|
|
30
65
|
|
|
31
66
|
# OAuth callback routes (paths match OmniAuth provider names)
|
|
@@ -42,20 +77,19 @@ Collavre::Engine.routes.draw do
|
|
|
42
77
|
resources :contacts, only: [ :destroy ]
|
|
43
78
|
resources :devices, only: [ :create ]
|
|
44
79
|
|
|
45
|
-
resources :inbox_items, path: "inbox", only: [ :index, :update, :destroy ] do
|
|
46
|
-
get :count, on: :collection
|
|
47
|
-
end
|
|
48
|
-
|
|
49
80
|
resources :user_themes, only: [ :index, :create, :destroy ] do
|
|
50
81
|
member do
|
|
51
82
|
post :apply
|
|
52
83
|
end
|
|
53
84
|
end
|
|
54
85
|
|
|
55
|
-
resources :
|
|
86
|
+
resources :llm_models, only: [ :destroy ]
|
|
56
87
|
|
|
57
88
|
resources :creative_imports, only: [ :create ]
|
|
58
89
|
resources :tasks, only: [] do
|
|
90
|
+
collection do
|
|
91
|
+
get :active_statuses
|
|
92
|
+
end
|
|
59
93
|
member do
|
|
60
94
|
post :cancel
|
|
61
95
|
end
|
|
@@ -115,17 +149,18 @@ Collavre::Engine.routes.draw do
|
|
|
115
149
|
collection do
|
|
116
150
|
post :reorder
|
|
117
151
|
post :link_drop
|
|
152
|
+
patch :next_last_visited_sequence
|
|
118
153
|
get :append_as_parent, to: "creatives#append_as_parent", as: :append_as_parent_creative
|
|
119
154
|
get :append_below, to: "creatives#append_below", as: :append_below_creative
|
|
120
155
|
get :export_markdown
|
|
121
156
|
end
|
|
122
157
|
member do
|
|
158
|
+
patch :remember_last_visited
|
|
123
159
|
get :children
|
|
124
160
|
post :request_permission, to: "creatives#request_permission"
|
|
125
161
|
post :unconvert
|
|
126
162
|
patch :archive
|
|
127
163
|
patch :unarchive
|
|
128
|
-
get :parent_suggestions
|
|
129
164
|
get :slide_view
|
|
130
165
|
get :contexts
|
|
131
166
|
patch :update_contexts
|
|
@@ -136,10 +171,11 @@ Collavre::Engine.routes.draw do
|
|
|
136
171
|
|
|
137
172
|
resources :emails, only: [ :index, :show ]
|
|
138
173
|
resource :invite, only: [ :show, :create ]
|
|
139
|
-
|
|
140
174
|
post "/creative_expanded_states/toggle", to: "user_creative_preferences#toggle"
|
|
141
|
-
|
|
175
|
+
match "/creatives/:creative_id/user_creative_preferences/update_last_topic", to: "user_creative_preferences#update_last_topic", via: [ :post, :patch ], as: :update_last_topic
|
|
142
176
|
post "/comment_read_pointers/update", to: "comment_read_pointers#update"
|
|
177
|
+
post "/notices/:key/dismiss", to: "notices#dismiss", as: :dismiss_notice
|
|
178
|
+
delete "/notices", to: "notices#restore_all", as: :restore_notices
|
|
143
179
|
|
|
144
180
|
# Agent API (Claude Channel MCP plugin)
|
|
145
181
|
namespace :api do
|
|
@@ -148,16 +184,6 @@ Collavre::Engine.routes.draw do
|
|
|
148
184
|
post "agent/reply", to: "agents#reply"
|
|
149
185
|
post "agent/notify", to: "agents#notify"
|
|
150
186
|
delete "agent/:id", to: "agents#destroy"
|
|
151
|
-
|
|
152
|
-
# Mobile voice companion (Android): poll Inbox#System messages, read aloud,
|
|
153
|
-
# reply to the origin topic; a cold mic press starts work in Inbox#Main.
|
|
154
|
-
namespace :mobile do
|
|
155
|
-
post "voice_commands", to: "voice_commands#create"
|
|
156
|
-
get "agent_events", to: "agent_events#index"
|
|
157
|
-
post "agent_events/:id/respond", to: "agent_events#respond"
|
|
158
|
-
post "agent_events/:id/read", to: "agent_events#read"
|
|
159
|
-
post "devices", to: "devices#create"
|
|
160
|
-
end
|
|
161
187
|
end
|
|
162
188
|
end
|
|
163
189
|
|
|
@@ -2,7 +2,7 @@ class BackfillCreativeInCommentInboxItems < ActiveRecord::Migration[8.0]
|
|
|
2
2
|
disable_ddl_transaction!
|
|
3
3
|
|
|
4
4
|
def up
|
|
5
|
-
|
|
5
|
+
inbox_item_class.where(message_key: [ "inbox.comment_added", "inbox.user_mentioned" ]).find_each do |item|
|
|
6
6
|
params = item.message_params || {}
|
|
7
7
|
next if params["creative"].present?
|
|
8
8
|
|
|
@@ -24,4 +24,12 @@ class BackfillCreativeInCommentInboxItems < ActiveRecord::Migration[8.0]
|
|
|
24
24
|
def down
|
|
25
25
|
# no-op
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def inbox_item_class
|
|
31
|
+
Class.new(ActiveRecord::Base) do
|
|
32
|
+
self.table_name = "inbox_items"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
27
35
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class AddCreativeIdIndexToComments < ActiveRecord::Migration[8.1]
|
|
2
|
+
# The chat timeline now orders by comments.id (not created_at) to survive
|
|
3
|
+
# cross-process clock skew. A composite index on [creative_id, id] lets the DB
|
|
4
|
+
# satisfy the per-creative filter and the id-ordered LIMIT/range paging from
|
|
5
|
+
# the index, instead of scanning creative_id then sorting by id.
|
|
6
|
+
#
|
|
7
|
+
# Built CONCURRENTLY on PostgreSQL so the deploy never takes a write-blocking
|
|
8
|
+
# lock on the hot comments table while the index builds. Requires running
|
|
9
|
+
# outside a transaction. SQLite (dev/test) has no concurrent algorithm, so we
|
|
10
|
+
# omit it there; if_not_exists keeps the build idempotent on both.
|
|
11
|
+
disable_ddl_transaction!
|
|
12
|
+
|
|
13
|
+
# Explicit up/down: `change` auto-reverses add_index by forwarding every option
|
|
14
|
+
# (including if_not_exists:) to remove_index, which rejects it and fails rollback.
|
|
15
|
+
def up
|
|
16
|
+
add_index :comments, [ :creative_id, :id ],
|
|
17
|
+
name: "index_comments_on_creative_id_and_id",
|
|
18
|
+
algorithm: concurrent_algorithm,
|
|
19
|
+
if_not_exists: true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def down
|
|
23
|
+
remove_index :comments,
|
|
24
|
+
name: "index_comments_on_creative_id_and_id",
|
|
25
|
+
algorithm: concurrent_algorithm,
|
|
26
|
+
if_exists: true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def concurrent_algorithm
|
|
32
|
+
:concurrently if connection.adapter_name.match?(/postgres/i)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class AddCreativePrivateIdIndexToComments < ActiveRecord::Migration[8.1]
|
|
2
|
+
# Every unread/badge count is "comments of this creative, public only, newer
|
|
3
|
+
# than a watermark" — `WHERE creative_id = ? AND private = false [AND id > ?]`.
|
|
4
|
+
# index_comments_on_creative_id_and_id can serve the creative filter and the
|
|
5
|
+
# id ordering but not the private filter, so the planner still had to visit
|
|
6
|
+
# every row of a busy creative to discard private ones.
|
|
7
|
+
#
|
|
8
|
+
# Production PostgreSQL gets the smallest useful structure: a partial index
|
|
9
|
+
# over public rows only. SQLite renders `where(private: false)` as
|
|
10
|
+
# `private = 0`, which does not use a partial predicate recorded as
|
|
11
|
+
# `private = false`, so dev, test, and desktop use an equivalent three-column
|
|
12
|
+
# index instead.
|
|
13
|
+
#
|
|
14
|
+
# index_comments_on_creative_id_and_id stays: paging does not include the
|
|
15
|
+
# public-only predicate, and SQLite cannot skip the fallback index's leading
|
|
16
|
+
# `private` column for a plain `ORDER BY id` query.
|
|
17
|
+
disable_ddl_transaction!
|
|
18
|
+
|
|
19
|
+
# Explicit up/down: `change` auto-reverses add_index by forwarding every option
|
|
20
|
+
# (including if_not_exists:) to remove_index, which rejects it and fails rollback.
|
|
21
|
+
def up
|
|
22
|
+
options = { name: index_name, if_not_exists: true }
|
|
23
|
+
|
|
24
|
+
if postgresql?
|
|
25
|
+
options.merge!(where: "private = false", algorithm: :concurrently)
|
|
26
|
+
add_index :comments, [ :creative_id, :id ], **options
|
|
27
|
+
else
|
|
28
|
+
add_index :comments, [ :creative_id, :private, :id ], **options
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def down
|
|
33
|
+
options = { name: index_name, algorithm: concurrent_algorithm, if_exists: true }
|
|
34
|
+
remove_index :comments, **options
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def index_name
|
|
40
|
+
"index_comments_on_creative_id_and_private_and_id"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def postgresql?
|
|
44
|
+
connection.adapter_name.match?(/postgres/i)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def concurrent_algorithm
|
|
48
|
+
:concurrently if postgresql?
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class AddAsyncCommentNotificationState < ActiveRecord::Migration[8.1]
|
|
2
|
+
disable_ddl_transaction!
|
|
3
|
+
|
|
4
|
+
def up
|
|
5
|
+
add_column :comments, :notification_key, :string unless column_exists?(:comments, :notification_key)
|
|
6
|
+
unless column_exists?(:comments, :notification_revision)
|
|
7
|
+
add_column :comments, :notification_revision, :integer, default: 0, null: false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
create_table :comment_notification_deliveries, if_not_exists: true do |t|
|
|
11
|
+
t.string :delivery_key, null: false
|
|
12
|
+
t.bigint :inbox_comment_id, null: false
|
|
13
|
+
t.bigint :recipient_id, null: false
|
|
14
|
+
t.text :message, null: false
|
|
15
|
+
t.text :link
|
|
16
|
+
t.string :push_claim_token
|
|
17
|
+
t.datetime :push_claimed_at
|
|
18
|
+
t.datetime :push_enqueued_at
|
|
19
|
+
t.timestamps
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
add_index :comments,
|
|
23
|
+
:notification_key,
|
|
24
|
+
unique: true,
|
|
25
|
+
where: "notification_key IS NOT NULL",
|
|
26
|
+
algorithm: concurrent_algorithm,
|
|
27
|
+
if_not_exists: true
|
|
28
|
+
|
|
29
|
+
add_index :comment_notification_deliveries,
|
|
30
|
+
:delivery_key,
|
|
31
|
+
unique: true,
|
|
32
|
+
algorithm: concurrent_algorithm,
|
|
33
|
+
if_not_exists: true
|
|
34
|
+
add_index :comment_notification_deliveries,
|
|
35
|
+
[ :push_enqueued_at, :push_claimed_at ],
|
|
36
|
+
name: "index_comment_notification_deliveries_pending",
|
|
37
|
+
algorithm: concurrent_algorithm,
|
|
38
|
+
if_not_exists: true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def down
|
|
42
|
+
remove_index :comment_notification_deliveries,
|
|
43
|
+
name: "index_comment_notification_deliveries_pending",
|
|
44
|
+
algorithm: concurrent_algorithm,
|
|
45
|
+
if_exists: true
|
|
46
|
+
remove_index :comment_notification_deliveries,
|
|
47
|
+
:delivery_key,
|
|
48
|
+
algorithm: concurrent_algorithm,
|
|
49
|
+
if_exists: true
|
|
50
|
+
remove_index :comments,
|
|
51
|
+
:notification_key,
|
|
52
|
+
algorithm: concurrent_algorithm,
|
|
53
|
+
if_exists: true
|
|
54
|
+
drop_table :comment_notification_deliveries, if_exists: true
|
|
55
|
+
remove_column :comments, :notification_revision if column_exists?(:comments, :notification_revision)
|
|
56
|
+
remove_column :comments, :notification_key if column_exists?(:comments, :notification_key)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def concurrent_algorithm
|
|
62
|
+
:concurrently if connection.adapter_name.match?(/postgres/i)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Record what a "⏳ waiting on the topic slot" notice speaks for, instead of
|
|
2
|
+
# inferring it from whichever sibling notices happen to still stand.
|
|
3
|
+
#
|
|
4
|
+
# A topic gets one deduplicated notice while coalescing is on and one notice per
|
|
5
|
+
# deferral while it is off, so the two kinds coexist whenever the policy changes
|
|
6
|
+
# mid-wait or differs per agent. Reading "no sibling notice left" as "this one
|
|
7
|
+
# stood for the whole topic" then inverts ownership: deleting the shared notice
|
|
8
|
+
# is classified as deleting a per-deferral one and cancels the newest queued
|
|
9
|
+
# waiter — which may be the one the *other* notice speaks for.
|
|
10
|
+
#
|
|
11
|
+
# Nullable with no backfill on purpose: a notice already on screen was posted by
|
|
12
|
+
# the pre-existing code and there is nothing on the row to say which kind it was.
|
|
13
|
+
# Comment#cancel_queued_tasks_for_waiting_notice keeps its old behaviour for
|
|
14
|
+
# those, so an in-flight wait is neither widened nor silently disarmed.
|
|
15
|
+
class AddWaitingNoticeScopeToComments < ActiveRecord::Migration[8.1]
|
|
16
|
+
def up
|
|
17
|
+
unless column_exists?(:comments, :waiting_notice_scope)
|
|
18
|
+
add_column :comments, :waiting_notice_scope, :string
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
unless column_exists?(:comments, :waiting_notice_task_id)
|
|
22
|
+
add_column :comments, :waiting_notice_task_id, :integer
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def down
|
|
27
|
+
remove_column :comments, :waiting_notice_task_id if column_exists?(:comments, :waiting_notice_task_id)
|
|
28
|
+
remove_column :comments, :waiting_notice_scope if column_exists?(:comments, :waiting_notice_scope)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Record which kind of "⏳ waiting" notice speaks for a waiter on the waiter
|
|
2
|
+
# itself, at the moment it is parked.
|
|
3
|
+
#
|
|
4
|
+
# The waiter and its notice commit in two steps, so asking the notices which
|
|
5
|
+
# waiters they own has a window where the answer is wrong: an opted-out waiter is
|
|
6
|
+
# already queued while its own notice does not exist yet, and a shared notice
|
|
7
|
+
# deleted in that window reads it as one of its own and cancels it. What speaks
|
|
8
|
+
# for a waiter is decided by the agent's coalescing policy when the row is
|
|
9
|
+
# created, not by which neighbouring rows happen to be visible later.
|
|
10
|
+
#
|
|
11
|
+
# Nullable with no backfill on purpose: a waiter parked by the pre-existing code
|
|
12
|
+
# says nothing about which notice spoke for it, and
|
|
13
|
+
# Comment#represented_queued_waiters keeps the old sibling-notice answer for
|
|
14
|
+
# those rows. Widening would discard work; narrowing would leave an in-flight
|
|
15
|
+
# wait with no stop control.
|
|
16
|
+
class AddWaitingNoticeScopeToTasks < ActiveRecord::Migration[8.1]
|
|
17
|
+
def up
|
|
18
|
+
return if column_exists?(:tasks, :waiting_notice_scope)
|
|
19
|
+
|
|
20
|
+
add_column :tasks, :waiting_notice_scope, :string
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def down
|
|
24
|
+
remove_column :tasks, :waiting_notice_scope if column_exists?(:tasks, :waiting_notice_scope)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class ChangeCreativeWorkspaceEnabledDefaultToTrue < ActiveRecord::Migration[8.0]
|
|
2
|
+
# Lightweight stub so the backfill does not depend on the app model.
|
|
3
|
+
class MigrationUser < ActiveRecord::Base
|
|
4
|
+
self.table_name = :users
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def up
|
|
8
|
+
change_column_default :users, :creative_workspace_enabled, from: false, to: true
|
|
9
|
+
|
|
10
|
+
# The preference shipped as opt-in only days ago, so every stored `false`
|
|
11
|
+
# is the old default rather than a deliberate opt-out.
|
|
12
|
+
MigrationUser.where(creative_workspace_enabled: false).update_all(creative_workspace_enabled: true)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def down
|
|
16
|
+
raise ActiveRecord::IrreversibleMigration, "cannot distinguish backfilled defaults from post-migration user opt-ins"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class CreateLlmModels < ActiveRecord::Migration[8.1]
|
|
2
|
+
MAX_SUGGESTIONS = 100
|
|
3
|
+
MAX_VENDOR_LENGTH = 255
|
|
4
|
+
MAX_NAME_LENGTH = 255
|
|
5
|
+
|
|
6
|
+
DEFAULT_MODELS = {
|
|
7
|
+
"google" => [
|
|
8
|
+
"gemini-3.1-flash-lite",
|
|
9
|
+
"gemini-1.5-flash",
|
|
10
|
+
"gemini-1.5-pro"
|
|
11
|
+
]
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def up
|
|
15
|
+
create_table :llm_models do |t|
|
|
16
|
+
t.string :llm_vendor, null: false, limit: MAX_VENDOR_LENGTH
|
|
17
|
+
t.string :name, null: false, limit: MAX_NAME_LENGTH
|
|
18
|
+
t.references :created_by,
|
|
19
|
+
null: true,
|
|
20
|
+
foreign_key: { to_table: :users, on_delete: :nullify }
|
|
21
|
+
t.timestamps
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
add_index :llm_models, [ :llm_vendor, :name ], unique: true
|
|
25
|
+
|
|
26
|
+
llm_model_class = Class.new(ActiveRecord::Base) do
|
|
27
|
+
self.table_name = "llm_models"
|
|
28
|
+
end
|
|
29
|
+
user_class = Class.new(ActiveRecord::Base) do
|
|
30
|
+
self.table_name = "users"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
models = DEFAULT_MODELS.flat_map do |vendor, names|
|
|
34
|
+
names.map { |name| [ vendor, name ] }
|
|
35
|
+
end
|
|
36
|
+
models.concat(
|
|
37
|
+
user_class.where.not(llm_vendor: [ nil, "" ])
|
|
38
|
+
.where.not(llm_model: [ nil, "" ])
|
|
39
|
+
.distinct
|
|
40
|
+
.pluck(:llm_vendor, :llm_model)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
models.each do |vendor, name|
|
|
44
|
+
vendor = vendor.to_s.strip.downcase
|
|
45
|
+
name = name.to_s.strip
|
|
46
|
+
next if vendor.blank? || name.blank?
|
|
47
|
+
next if vendor.length > MAX_VENDOR_LENGTH || name.length > MAX_NAME_LENGTH
|
|
48
|
+
|
|
49
|
+
llm_model_class.find_or_create_by!(
|
|
50
|
+
llm_vendor: vendor,
|
|
51
|
+
name: name
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
keep_ids = llm_model_class.order(updated_at: :desc, id: :desc)
|
|
56
|
+
.limit(MAX_SUGGESTIONS)
|
|
57
|
+
.select(:id)
|
|
58
|
+
llm_model_class.where.not(id: keep_ids).delete_all
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def down
|
|
62
|
+
drop_table :llm_models
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class CreateAgentGatewaysAndWorkspaces < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :agent_gateways do |t|
|
|
4
|
+
t.references :owner, null: false, foreign_key: { to_table: :users }
|
|
5
|
+
t.string :name, null: false
|
|
6
|
+
t.string :base_url, null: false
|
|
7
|
+
t.text :admin_key, null: false
|
|
8
|
+
t.text :completion_key, null: false
|
|
9
|
+
t.text :identity_secret
|
|
10
|
+
t.string :tenant_id, null: false, default: "collavre"
|
|
11
|
+
t.integer :workspace_mode, null: false, default: 0
|
|
12
|
+
t.boolean :active, null: false, default: true
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
add_index :agent_gateways, [ :owner_id, :name ], unique: true
|
|
16
|
+
|
|
17
|
+
add_reference :users, :agent_gateway, foreign_key: true
|
|
18
|
+
|
|
19
|
+
create_table :agent_workspaces do |t|
|
|
20
|
+
t.references :agent, null: false, foreign_key: { to_table: :users }
|
|
21
|
+
t.references :user, foreign_key: { to_table: :users }
|
|
22
|
+
t.references :agent_gateway, null: false, foreign_key: true
|
|
23
|
+
t.string :proxy_user_id, null: false
|
|
24
|
+
t.string :manifest_token, null: false
|
|
25
|
+
t.text :callback_token, null: false
|
|
26
|
+
t.timestamps
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
add_index :agent_workspaces, :manifest_token, unique: true
|
|
30
|
+
add_index :agent_workspaces, [ :agent_id, :agent_gateway_id ],
|
|
31
|
+
unique: true, where: "user_id IS NULL", name: "idx_agent_workspaces_shared"
|
|
32
|
+
add_index :agent_workspaces, [ :agent_id, :user_id, :agent_gateway_id ],
|
|
33
|
+
unique: true, where: "user_id IS NOT NULL", name: "idx_agent_workspaces_per_user"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
class HashAgentWorkspaceCallbackTokens < ActiveRecord::Migration[8.0]
|
|
6
|
+
PREFIX = "sha256$"
|
|
7
|
+
|
|
8
|
+
class AgentWorkspaceRecord < ActiveRecord::Base
|
|
9
|
+
self.table_name = "agent_workspaces"
|
|
10
|
+
|
|
11
|
+
encrypts :callback_token, deterministic: false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class AccessTokenRecord < ActiveRecord::Base
|
|
15
|
+
self.table_name = "oauth_access_tokens"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def up
|
|
19
|
+
each_workspace_token do |plaintext|
|
|
20
|
+
token = AccessTokenRecord.find_by(token: plaintext)
|
|
21
|
+
token&.update_columns(token: encoded(plaintext))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def down
|
|
26
|
+
each_workspace_token do |plaintext|
|
|
27
|
+
token = AccessTokenRecord.find_by(token: encoded(plaintext))
|
|
28
|
+
token&.update_columns(token: plaintext)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def each_workspace_token
|
|
35
|
+
AgentWorkspaceRecord.find_each do |workspace|
|
|
36
|
+
plaintext = workspace.callback_token
|
|
37
|
+
yield plaintext if plaintext.present?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def encoded(plaintext)
|
|
42
|
+
"#{PREFIX}#{Digest::SHA256.hexdigest(plaintext)}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
class EncryptAgentWorkspaceManifestTokens < ActiveRecord::Migration[8.0]
|
|
6
|
+
class AgentWorkspaceRecord < ActiveRecord::Base
|
|
7
|
+
self.table_name = "agent_workspaces"
|
|
8
|
+
|
|
9
|
+
encrypts :manifest_token, deterministic: false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def up
|
|
13
|
+
add_column :agent_workspaces, :manifest_token_digest, :string
|
|
14
|
+
|
|
15
|
+
say_with_time "Encrypting agent workspace manifest capabilities" do
|
|
16
|
+
transform_tokens(encrypt: true)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
change_column_null :agent_workspaces, :manifest_token_digest, false
|
|
20
|
+
remove_index :agent_workspaces, :manifest_token
|
|
21
|
+
add_index :agent_workspaces, :manifest_token_digest, unique: true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def down
|
|
25
|
+
remove_index :agent_workspaces, :manifest_token_digest
|
|
26
|
+
|
|
27
|
+
say_with_time "Restoring plaintext agent workspace manifest capabilities" do
|
|
28
|
+
transform_tokens(encrypt: false)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
remove_column :agent_workspaces, :manifest_token_digest
|
|
32
|
+
add_index :agent_workspaces, :manifest_token, unique: true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def transform_tokens(encrypt:)
|
|
38
|
+
connection = ActiveRecord::Base.connection
|
|
39
|
+
encryptor = AgentWorkspaceRecord.type_for_attribute(:manifest_token)
|
|
40
|
+
|
|
41
|
+
AgentWorkspaceRecord.reset_column_information
|
|
42
|
+
rows = connection.select_all("SELECT id, manifest_token FROM agent_workspaces").to_a
|
|
43
|
+
rows.each do |row|
|
|
44
|
+
raw_token = row.fetch("manifest_token")
|
|
45
|
+
plaintext = encrypted?(raw_token) ? encryptor.deserialize(raw_token) : raw_token
|
|
46
|
+
stored = encrypt ? encryptor.serialize(plaintext) : plaintext
|
|
47
|
+
assignments = [ "manifest_token = #{connection.quote(stored)}" ]
|
|
48
|
+
assignments << "manifest_token_digest = #{connection.quote(Digest::SHA256.hexdigest(plaintext))}" if encrypt
|
|
49
|
+
|
|
50
|
+
connection.execute(
|
|
51
|
+
"UPDATE agent_workspaces SET #{assignments.join(', ')} WHERE id = #{connection.quote(row.fetch('id'))}"
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def encrypted?(value)
|
|
57
|
+
value.start_with?("{") && value.include?('"p":')
|
|
58
|
+
end
|
|
59
|
+
end
|