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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The CLI proxy now carries two identity axes: X-CLI-Proxy-User-ID selects the
|
|
4
|
+
# OS worker holding the engine credentials, and X-CLI-Proxy-Workspace-ID selects
|
|
5
|
+
# a path workspace below that worker's HOME. Split the single proxy_user_id into
|
|
6
|
+
# those axes so engine login is per Collavre user while skills and workspace
|
|
7
|
+
# config stay per agent.
|
|
8
|
+
#
|
|
9
|
+
# Expand only. bin/docker-entrypoint runs db:migrate while the previous release
|
|
10
|
+
# is still serving requests against the same database, so proxy_user_id has to
|
|
11
|
+
# stay readable and writable until every process runs the new code. The new
|
|
12
|
+
# columns are therefore nullable here and the old one merely loses its NOT NULL,
|
|
13
|
+
# which also keeps a Kamal rollback working. The contract half — dropping
|
|
14
|
+
# proxy_user_id and adding NOT NULL to the two new columns — belongs to a later
|
|
15
|
+
# release, once no process writes the legacy column anymore.
|
|
16
|
+
class SplitAgentWorkspaceIdentityAxes < ActiveRecord::Migration[8.0]
|
|
17
|
+
class MigrationWorkspace < ActiveRecord::Base
|
|
18
|
+
self.table_name = "agent_workspaces"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def up
|
|
22
|
+
add_column :agent_workspaces, :proxy_credential_id, :string
|
|
23
|
+
add_column :agent_workspaces, :proxy_workspace_id, :string
|
|
24
|
+
|
|
25
|
+
MigrationWorkspace.reset_column_information
|
|
26
|
+
MigrationWorkspace.find_each do |workspace|
|
|
27
|
+
workspace.update_columns(
|
|
28
|
+
proxy_workspace_id: "agent-#{workspace.agent_id}",
|
|
29
|
+
proxy_credential_id: credential_id_for(workspace)
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
change_column_null :agent_workspaces, :proxy_user_id, true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def down
|
|
37
|
+
MigrationWorkspace.reset_column_information
|
|
38
|
+
MigrationWorkspace.where(proxy_user_id: nil).find_each do |workspace|
|
|
39
|
+
workspace.update_columns(proxy_user_id: legacy_proxy_user_id_for(workspace))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
change_column_null :agent_workspaces, :proxy_user_id, false
|
|
43
|
+
remove_column :agent_workspaces, :proxy_workspace_id
|
|
44
|
+
remove_column :agent_workspaces, :proxy_credential_id
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def credential_id_for(workspace)
|
|
50
|
+
workspace.user_id ? "user-#{workspace.user_id}" : "agent-#{workspace.agent_id}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def legacy_proxy_user_id_for(workspace)
|
|
54
|
+
return "agent-#{workspace.agent_id}" unless workspace.user_id
|
|
55
|
+
|
|
56
|
+
"agent-#{workspace.agent_id}--user-#{workspace.user_id}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class SeedCodexCustomProxyLlmModels < ActiveRecord::Migration[8.1]
|
|
2
|
+
MAX_SUGGESTIONS = 100
|
|
3
|
+
DEFAULT_MODELS = [
|
|
4
|
+
"paperclip/codex_custom/anthropic/claude-sonnet-4.5",
|
|
5
|
+
"paperclip/codex_custom/openai/gpt-5"
|
|
6
|
+
].freeze
|
|
7
|
+
|
|
8
|
+
def up
|
|
9
|
+
DEFAULT_MODELS.each do |name|
|
|
10
|
+
model = llm_model_class.find_or_create_by!(llm_vendor: "cli_proxy", name:)
|
|
11
|
+
model.touch unless model.previously_new_record?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
keep_ids = llm_model_class.order(updated_at: :desc, id: :desc).limit(MAX_SUGGESTIONS).select(:id)
|
|
15
|
+
llm_model_class.where.not(id: keep_ids).delete_all
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Keep shared, user-created model suggestions intact if this data migration is rolled back.
|
|
19
|
+
def down; end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def llm_model_class
|
|
24
|
+
@llm_model_class ||= Class.new(ActiveRecord::Base) do
|
|
25
|
+
self.table_name = "llm_models"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddDesktopManagedToAgentGateways < ActiveRecord::Migration[8.1]
|
|
4
|
+
def up
|
|
5
|
+
add_column :agent_gateways, :desktop_managed, :boolean, default: false, null: false unless column_exists?(:agent_gateways, :desktop_managed)
|
|
6
|
+
backfill_legacy_desktop_gateway
|
|
7
|
+
add_index :agent_gateways, :desktop_managed, unique: true, where: "desktop_managed", name: "index_agent_gateways_on_desktop_managed" unless index_exists?(:agent_gateways, name: "index_agent_gateways_on_desktop_managed")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def down
|
|
11
|
+
remove_index :agent_gateways, name: "index_agent_gateways_on_desktop_managed" if index_exists?(:agent_gateways, name: "index_agent_gateways_on_desktop_managed")
|
|
12
|
+
remove_column :agent_gateways, :desktop_managed if column_exists?(:agent_gateways, :desktop_managed)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def backfill_legacy_desktop_gateway
|
|
18
|
+
# Legacy records have no dedicated marker. A generated CLI preset linked
|
|
19
|
+
# to the gateway is the durable signature; tenant ID and display fields
|
|
20
|
+
# alone remain user-editable and must not cause a gateway to be adopted.
|
|
21
|
+
legacy_gateway_ids = select_values(<<~SQL.squish)
|
|
22
|
+
SELECT id FROM agent_gateways
|
|
23
|
+
WHERE desktop_managed = FALSE
|
|
24
|
+
AND base_url LIKE 'http://127.0.0.1:%'
|
|
25
|
+
AND EXISTS (
|
|
26
|
+
SELECT 1 FROM users
|
|
27
|
+
WHERE users.agent_gateway_id = agent_gateways.id
|
|
28
|
+
AND users.created_by_id = agent_gateways.owner_id
|
|
29
|
+
AND users.email IN (
|
|
30
|
+
'collavre-desktop-claude-code@ai.local',
|
|
31
|
+
'collavre-desktop-codex@ai.local'
|
|
32
|
+
)
|
|
33
|
+
AND users.llm_vendor = 'cli_proxy'
|
|
34
|
+
)
|
|
35
|
+
SQL
|
|
36
|
+
return unless legacy_gateway_ids.one?
|
|
37
|
+
|
|
38
|
+
execute "UPDATE agent_gateways SET desktop_managed = TRUE WHERE id = #{connection.quote(legacy_gateway_ids.first)}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddDesktopPresetAdapterToUsers < ActiveRecord::Migration[8.1]
|
|
4
|
+
PRESET_EMAILS = {
|
|
5
|
+
"claude" => "collavre-desktop-claude-code@ai.local",
|
|
6
|
+
"codex" => "collavre-desktop-codex@ai.local"
|
|
7
|
+
}.freeze
|
|
8
|
+
|
|
9
|
+
def up
|
|
10
|
+
add_column :users, :desktop_preset_adapter, :string unless column_exists?(:users, :desktop_preset_adapter)
|
|
11
|
+
backfill_legacy_desktop_presets
|
|
12
|
+
add_index :users, :desktop_preset_adapter, unique: true,
|
|
13
|
+
where: "desktop_preset_adapter IS NOT NULL",
|
|
14
|
+
name: "index_users_on_desktop_preset_adapter" unless index_exists?(:users, name: "index_users_on_desktop_preset_adapter")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def down
|
|
18
|
+
remove_index :users, name: "index_users_on_desktop_preset_adapter" if index_exists?(:users, name: "index_users_on_desktop_preset_adapter")
|
|
19
|
+
remove_column :users, :desktop_preset_adapter if column_exists?(:users, :desktop_preset_adapter)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def backfill_legacy_desktop_presets
|
|
25
|
+
PRESET_EMAILS.each do |adapter, email|
|
|
26
|
+
execute <<~SQL.squish
|
|
27
|
+
UPDATE users
|
|
28
|
+
SET desktop_preset_adapter = #{connection.quote(adapter)}
|
|
29
|
+
WHERE desktop_preset_adapter IS NULL
|
|
30
|
+
AND email = #{connection.quote(email)}
|
|
31
|
+
AND llm_vendor = 'cli_proxy'
|
|
32
|
+
AND agent_gateway_id IN (
|
|
33
|
+
SELECT id FROM agent_gateways WHERE desktop_managed = TRUE
|
|
34
|
+
)
|
|
35
|
+
SQL
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
class RemoveWorkflowColumnsFromTasks < ActiveRecord::Migration[8.1]
|
|
2
|
+
# The /work command and its WorkflowExecutor are gone, and they were the only
|
|
3
|
+
# producers of parent_task_id / workflow_context / workflow_state / retry_count.
|
|
4
|
+
# Nothing reads these columns anymore, so drop them.
|
|
5
|
+
|
|
6
|
+
IN_FLIGHT_STATUSES = %w[pending queued running delegated pending_approval].freeze
|
|
7
|
+
|
|
8
|
+
# AiAgentJob reserves the agent's ResourceTracker slot under task.id once the
|
|
9
|
+
# job starts, so only these statuses can still hold one; pending and queued
|
|
10
|
+
# never reached reserve!. release! on an unreserved id is a no-op anyway.
|
|
11
|
+
RESERVED_STATUSES = %w[running delegated pending_approval].freeze
|
|
12
|
+
|
|
13
|
+
def up
|
|
14
|
+
cancel_in_flight_workflow_tasks
|
|
15
|
+
|
|
16
|
+
remove_foreign_key :tasks, :tasks, column: :parent_task_id, if_exists: true
|
|
17
|
+
remove_column :tasks, :parent_task_id, :integer
|
|
18
|
+
remove_column :tasks, :workflow_context, :text
|
|
19
|
+
remove_column :tasks, :workflow_state, :json
|
|
20
|
+
remove_column :tasks, :retry_count, :integer, default: 0, null: false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def down
|
|
24
|
+
add_column :tasks, :parent_task_id, :integer
|
|
25
|
+
add_column :tasks, :workflow_context, :text
|
|
26
|
+
add_column :tasks, :workflow_state, :json
|
|
27
|
+
add_column :tasks, :retry_count, :integer, default: 0, null: false
|
|
28
|
+
add_index :tasks, :parent_task_id
|
|
29
|
+
add_foreign_key :tasks, :tasks, column: :parent_task_id, on_delete: :nullify
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Any workflow still mid-flight loses the state that would let it advance.
|
|
35
|
+
# Cancel those rows before the columns disappear so they cannot linger as
|
|
36
|
+
# permanently "running" tasks holding a topic slot.
|
|
37
|
+
def cancel_in_flight_workflow_tasks
|
|
38
|
+
rows = select_all(<<~SQL.squish).to_a
|
|
39
|
+
SELECT id, agent_id, topic_id, creative_id, status
|
|
40
|
+
FROM tasks
|
|
41
|
+
WHERE status IN (#{quoted_in_flight_statuses})
|
|
42
|
+
AND (parent_task_id IS NOT NULL
|
|
43
|
+
OR trigger_event_name IN ('work_command', 'workflow_subtask'))
|
|
44
|
+
SQL
|
|
45
|
+
return if rows.empty?
|
|
46
|
+
|
|
47
|
+
ids = rows.map { |row| row["id"].to_i }.join(", ")
|
|
48
|
+
execute(<<~SQL.squish)
|
|
49
|
+
UPDATE tasks
|
|
50
|
+
SET status = 'cancelled', updated_at = #{connection.quote(Time.current)}
|
|
51
|
+
WHERE id IN (#{ids})
|
|
52
|
+
SQL
|
|
53
|
+
|
|
54
|
+
release_slots(rows)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# A raw UPDATE only settles the row. Every other bulk cancellation in the app
|
|
58
|
+
# (TasksController#cancel, CancelOfflineDelegatedTasksJob, stuck recovery)
|
|
59
|
+
# also hands back the agent's ResourceTracker slot and drains the topic queue,
|
|
60
|
+
# because these tasks were paused with no live worker to run AiAgentJob's
|
|
61
|
+
# ensure-block. Skipping that here would keep a cancelled task counting
|
|
62
|
+
# against the agent's max_concurrent_jobs until the cache entry expires an
|
|
63
|
+
# hour later — long enough, with a limit of 1, to freeze the agent right after
|
|
64
|
+
# deploy. Reaching into app classes from a migration is unusual, but the
|
|
65
|
+
# reservation lives in Rails.cache and there is no SQL equivalent.
|
|
66
|
+
def release_slots(rows)
|
|
67
|
+
return unless defined?(Collavre::Orchestration::ResourceTracker)
|
|
68
|
+
|
|
69
|
+
rows.group_by { |row| row["agent_id"] }.each do |agent_id, agent_rows|
|
|
70
|
+
next if agent_id.blank?
|
|
71
|
+
|
|
72
|
+
agent = Collavre::User.find_by(id: agent_id)
|
|
73
|
+
next unless agent
|
|
74
|
+
|
|
75
|
+
tracker = Collavre::Orchestration::ResourceTracker.for(agent)
|
|
76
|
+
agent_rows.each do |row|
|
|
77
|
+
tracker.release!(row["id"]) if RESERVED_STATUSES.include?(row["status"])
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
drain_topics(rows)
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
# Cache/orchestration cleanup is best effort: stuck recovery still sweeps
|
|
84
|
+
# whatever is left. Never fail the schema change over it.
|
|
85
|
+
say("Slot release after workflow cancellation failed: #{e.class}: #{e.message}")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Freeing the slot is only half of it — a waiter queued behind the cancelled
|
|
89
|
+
# holder stays queued until stuck recovery notices, which can take minutes.
|
|
90
|
+
def drain_topics(rows)
|
|
91
|
+
rows.filter_map { |row| [ row["topic_id"], row["creative_id"] ] if row["topic_id"].present? }
|
|
92
|
+
.uniq
|
|
93
|
+
.each do |topic_id, creative_id|
|
|
94
|
+
Collavre::Orchestration::AgentOrchestrator.dequeue_next_for_topic(topic_id, creative_id)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def quoted_in_flight_statuses
|
|
99
|
+
IN_FLIGHT_STATUSES.map { |status| connection.quote(status) }.join(", ")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class RemoveAndroidDevices < ActiveRecord::Migration[8.0]
|
|
2
|
+
ANDROID_DEVICE_CONSTRAINT = "devices_no_android_device_type".freeze
|
|
3
|
+
|
|
4
|
+
def up
|
|
5
|
+
if connection.supports_validate_constraints?
|
|
6
|
+
# PostgreSQL can enforce a not-yet-validated constraint. This closes the
|
|
7
|
+
# rolling-deploy window before removing existing registrations: an old
|
|
8
|
+
# container may finish a request, but cannot recreate an Android token.
|
|
9
|
+
add_check_constraint :devices, "device_type != 2", name: ANDROID_DEVICE_CONSTRAINT, validate: false
|
|
10
|
+
execute "DELETE FROM devices WHERE device_type = 2"
|
|
11
|
+
validate_check_constraint :devices, name: ANDROID_DEVICE_CONSTRAINT
|
|
12
|
+
else
|
|
13
|
+
# SQLite rebuilds the table to add a check constraint, so it cannot add
|
|
14
|
+
# one while Android rows exist. The migration transaction serializes
|
|
15
|
+
# writers, making the delete and constraint addition atomic here.
|
|
16
|
+
execute "DELETE FROM devices WHERE device_type = 2"
|
|
17
|
+
add_check_constraint :devices, "device_type != 2", name: ANDROID_DEVICE_CONSTRAINT
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def down
|
|
22
|
+
raise ActiveRecord::IrreversibleMigration, "Android device tokens cannot be restored"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class DropInboxItems < ActiveRecord::Migration[8.0]
|
|
2
|
+
def up
|
|
3
|
+
drop_table :inbox_items
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def down
|
|
7
|
+
create_table :inbox_items do |t|
|
|
8
|
+
t.references :comment, foreign_key: { on_delete: :nullify }
|
|
9
|
+
t.references :creative, foreign_key: { on_delete: :nullify }
|
|
10
|
+
t.text :message
|
|
11
|
+
t.string :message_key
|
|
12
|
+
t.jsonb :message_params, default: {}, null: false
|
|
13
|
+
t.references :owner, null: false, foreign_key: { to_table: :users }
|
|
14
|
+
t.string :link
|
|
15
|
+
t.string :state, default: "new", null: false
|
|
16
|
+
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
add_index :inbox_items, :state
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ClearClaudeChannelPresenceRoutingExpressions < ActiveRecord::Migration[8.0]
|
|
4
|
+
def up
|
|
5
|
+
execute <<~SQL.squish
|
|
6
|
+
UPDATE users
|
|
7
|
+
SET routing_expression = NULL
|
|
8
|
+
WHERE llm_model = 'claude-code'
|
|
9
|
+
AND routing_expression = 'true'
|
|
10
|
+
SQL
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def down
|
|
14
|
+
# Presence rows, not routing_expression, now own Claude Channel liveness.
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
class AddLastTopicSaveFencesToUserCreativePreferences < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
add_column :user_creative_preferences, :last_topic_save_fence_issued, :bigint, null: false, default: 0
|
|
4
|
+
add_column :user_creative_preferences, :last_topic_save_fence_applied, :bigint, null: false, default: 0
|
|
5
|
+
end
|
|
6
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
class AddTopicToCommentReadPointers < ActiveRecord::Migration[8.0]
|
|
2
|
+
def up
|
|
3
|
+
add_reference :comment_read_pointers, :topic,
|
|
4
|
+
foreign_key: { to_table: :topics, on_delete: :cascade }, null: true
|
|
5
|
+
remove_index :comment_read_pointers, column: %i[user_id creative_id]
|
|
6
|
+
|
|
7
|
+
execute <<~SQL.squish
|
|
8
|
+
INSERT INTO comment_read_pointers
|
|
9
|
+
(user_id, creative_id, topic_id, last_read_comment_id, created_at, updated_at)
|
|
10
|
+
SELECT pointers.user_id, pointers.creative_id, topics.id,
|
|
11
|
+
pointers.last_read_comment_id, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
|
12
|
+
FROM comment_read_pointers AS pointers
|
|
13
|
+
INNER JOIN topics ON topics.creative_id = pointers.creative_id
|
|
14
|
+
WHERE pointers.topic_id IS NULL
|
|
15
|
+
SQL
|
|
16
|
+
|
|
17
|
+
add_index :comment_read_pointers, %i[user_id creative_id topic_id], unique: true,
|
|
18
|
+
name: "index_comment_read_pointers_on_user_creative_and_topic"
|
|
19
|
+
add_index :comment_read_pointers, %i[user_id creative_id], unique: true,
|
|
20
|
+
where: "topic_id IS NULL", name: "index_comment_read_pointers_on_legacy_pointer"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def down
|
|
24
|
+
remove_index :comment_read_pointers, name: "index_comment_read_pointers_on_legacy_pointer"
|
|
25
|
+
remove_index :comment_read_pointers, name: "index_comment_read_pointers_on_user_creative_and_topic"
|
|
26
|
+
consolidate_topic_watermarks
|
|
27
|
+
execute "DELETE FROM comment_read_pointers WHERE topic_id IS NOT NULL"
|
|
28
|
+
remove_reference :comment_read_pointers, :topic, foreign_key: true
|
|
29
|
+
add_index :comment_read_pointers, %i[user_id creative_id], unique: true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def consolidate_topic_watermarks
|
|
35
|
+
create_missing_legacy_pointers
|
|
36
|
+
|
|
37
|
+
execute <<~SQL.squish
|
|
38
|
+
UPDATE comment_read_pointers AS legacy_pointers
|
|
39
|
+
SET last_read_comment_id = COALESCE((
|
|
40
|
+
SELECT MIN(COALESCE(topic_pointers.last_read_comment_id, 0))
|
|
41
|
+
FROM topics
|
|
42
|
+
LEFT JOIN comment_read_pointers AS topic_pointers
|
|
43
|
+
ON topic_pointers.user_id = legacy_pointers.user_id
|
|
44
|
+
AND topic_pointers.creative_id = legacy_pointers.creative_id
|
|
45
|
+
AND topic_pointers.topic_id = topics.id
|
|
46
|
+
WHERE topics.creative_id = legacy_pointers.creative_id
|
|
47
|
+
), 0)
|
|
48
|
+
WHERE legacy_pointers.topic_id IS NULL
|
|
49
|
+
AND COALESCE(legacy_pointers.last_read_comment_id, 0) > COALESCE((
|
|
50
|
+
SELECT MIN(COALESCE(topic_pointers.last_read_comment_id, 0))
|
|
51
|
+
FROM topics
|
|
52
|
+
LEFT JOIN comment_read_pointers AS topic_pointers
|
|
53
|
+
ON topic_pointers.user_id = legacy_pointers.user_id
|
|
54
|
+
AND topic_pointers.creative_id = legacy_pointers.creative_id
|
|
55
|
+
AND topic_pointers.topic_id = topics.id
|
|
56
|
+
WHERE topics.creative_id = legacy_pointers.creative_id
|
|
57
|
+
), 0)
|
|
58
|
+
SQL
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def create_missing_legacy_pointers
|
|
62
|
+
execute <<~SQL.squish
|
|
63
|
+
INSERT INTO comment_read_pointers (user_id, creative_id, last_read_comment_id, created_at, updated_at)
|
|
64
|
+
SELECT pointer_pairs.user_id, pointer_pairs.creative_id,
|
|
65
|
+
0,
|
|
66
|
+
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
|
67
|
+
FROM (
|
|
68
|
+
SELECT DISTINCT user_id, creative_id
|
|
69
|
+
FROM comment_read_pointers
|
|
70
|
+
WHERE topic_id IS NOT NULL
|
|
71
|
+
) AS pointer_pairs
|
|
72
|
+
LEFT JOIN comment_read_pointers AS legacy_pointers
|
|
73
|
+
ON legacy_pointers.user_id = pointer_pairs.user_id
|
|
74
|
+
AND legacy_pointers.creative_id = pointer_pairs.creative_id
|
|
75
|
+
AND legacy_pointers.topic_id IS NULL
|
|
76
|
+
WHERE legacy_pointers.id IS NULL
|
|
77
|
+
SQL
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
class AddCreativeTopicPrivateIdIndexToComments < ActiveRecord::Migration[8.1]
|
|
2
|
+
# Badge fanout counts per-topic suffixes after a reader watermark. The former
|
|
3
|
+
# creative/private/id index still needs to scan every newer topic in a busy
|
|
4
|
+
# creative, while this index lets the database seek directly to that topic's
|
|
5
|
+
# suffix.
|
|
6
|
+
disable_ddl_transaction!
|
|
7
|
+
|
|
8
|
+
def up
|
|
9
|
+
options = { name: index_name, if_not_exists: true }
|
|
10
|
+
|
|
11
|
+
if postgresql?
|
|
12
|
+
options.merge!(where: "private = false", algorithm: :concurrently)
|
|
13
|
+
add_index :comments, %i[creative_id topic_id id], **options
|
|
14
|
+
else
|
|
15
|
+
add_index :comments, %i[creative_id private topic_id id], **options
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def down
|
|
20
|
+
options = { name: index_name, algorithm: concurrent_algorithm, if_exists: true }
|
|
21
|
+
remove_index :comments, **options
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def index_name
|
|
27
|
+
"index_comments_on_creative_topic_private_and_id"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def postgresql?
|
|
31
|
+
connection.adapter_name.match?(/postgres/i)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def concurrent_algorithm
|
|
35
|
+
:concurrently if postgresql?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class AddWatermarkIndexToCommentReadPointers < ActiveRecord::Migration[8.1]
|
|
2
|
+
# Both receipt paths — rendering a page and repainting after a read — select a
|
|
3
|
+
# creative's pointers by watermark range. The existing indexes are keyed by
|
|
4
|
+
# user first, so those range scans read every pointer row on the creative and
|
|
5
|
+
# filtered in memory. Pointer rows are now per user *and* per topic, so that
|
|
6
|
+
# set grows multiplicatively.
|
|
7
|
+
disable_ddl_transaction!
|
|
8
|
+
|
|
9
|
+
def up
|
|
10
|
+
add_index :comment_read_pointers, %i[creative_id last_read_comment_id],
|
|
11
|
+
name: index_name, if_not_exists: true, algorithm: concurrent_algorithm
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def down
|
|
15
|
+
remove_index :comment_read_pointers, name: index_name, if_exists: true, algorithm: concurrent_algorithm
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def index_name
|
|
21
|
+
"index_comment_read_pointers_on_creative_and_watermark"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def concurrent_algorithm
|
|
25
|
+
:concurrently if connection.adapter_name.match?(/postgres/i)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class AddPrivateRecipientTopicIndexToComments < ActiveRecord::Migration[8.1]
|
|
2
|
+
# Badge fanout asks which topics hold private comments for each recipient. The
|
|
3
|
+
# existing private index is keyed by topic before recipient, so answering that
|
|
4
|
+
# per user meant scanning every private comment on the creative.
|
|
5
|
+
disable_ddl_transaction!
|
|
6
|
+
|
|
7
|
+
RECIPIENT_COLUMNS = %i[user_id approver_id].freeze
|
|
8
|
+
|
|
9
|
+
def up
|
|
10
|
+
RECIPIENT_COLUMNS.each do |recipient_column|
|
|
11
|
+
add_index :comments, index_columns(recipient_column), **index_options(recipient_column).merge(if_not_exists: true)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def down
|
|
16
|
+
RECIPIENT_COLUMNS.each do |recipient_column|
|
|
17
|
+
remove_index :comments, name: index_name(recipient_column), if_exists: true, algorithm: concurrent_algorithm
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# On PostgreSQL the predicate carries `private`, so it stays out of the key.
|
|
24
|
+
def index_columns(recipient_column)
|
|
25
|
+
return [ :creative_id, recipient_column, :topic_id ] if postgresql?
|
|
26
|
+
|
|
27
|
+
[ :creative_id, :private, recipient_column, :topic_id ]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def index_options(recipient_column)
|
|
31
|
+
options = { name: index_name(recipient_column) }
|
|
32
|
+
return options unless postgresql?
|
|
33
|
+
|
|
34
|
+
options.merge(where: "private = true", algorithm: :concurrently)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def index_name(recipient_column)
|
|
38
|
+
"index_comments_on_creative_private_#{recipient_column}_and_topic"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def postgresql?
|
|
42
|
+
connection.adapter_name.match?(/postgres/i)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def concurrent_algorithm
|
|
46
|
+
:concurrently if postgresql?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class AddTopicAssignedAtToComments < ActiveRecord::Migration[8.1]
|
|
2
|
+
def up
|
|
3
|
+
add_column :comments, :topic_assigned_at, :datetime, precision: 6
|
|
4
|
+
execute "UPDATE comments SET topic_assigned_at = created_at"
|
|
5
|
+
change_column_null :comments, :topic_assigned_at, false
|
|
6
|
+
add_index :comments, %i[topic_id topic_assigned_at], name: "index_comments_on_topic_and_assigned_at"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def down
|
|
10
|
+
remove_index :comments, name: "index_comments_on_topic_and_assigned_at"
|
|
11
|
+
remove_column :comments, :topic_assigned_at
|
|
12
|
+
end
|
|
13
|
+
end
|
data/db/seeds.rb
CHANGED
|
@@ -17,54 +17,4 @@ module Collavre
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
Collavre::ChannelBotSeed.call
|
|
20
|
-
|
|
21
|
-
module Collavre
|
|
22
|
-
# Seeds the "Typo Corrector" agent. Unlike event-routed agents (e.g. the GitHub
|
|
23
|
-
# PR Analyzer) this agent has no routing_expression — it is never dispatched by
|
|
24
|
-
# comment events. It exists purely as a user-configurable holder for the LLM
|
|
25
|
-
# vendor/model/system_prompt that Collavre::TypoCorrector invokes directly.
|
|
26
|
-
module TypoCorrectorSeed
|
|
27
|
-
AGENT_EMAIL = "typo-corrector@collavre.local"
|
|
28
|
-
|
|
29
|
-
SYSTEM_PROMPT = <<~PROMPT
|
|
30
|
-
You are a typo correction engine. You receive a short piece of text the user
|
|
31
|
-
is currently typing and return ONLY spelling/typo fixes as a structured edit list.
|
|
32
|
-
|
|
33
|
-
Rules:
|
|
34
|
-
- Fix only spelling mistakes and obvious typos (transposed/missing/extra letters,
|
|
35
|
-
wrong jamo, mis-spaced words). Works for any language (ko, en, ja, zh, mixed).
|
|
36
|
-
- Do NOT rewrite style, grammar, word choice, tone, or punctuation.
|
|
37
|
-
- Do NOT change meaning. Do NOT translate.
|
|
38
|
-
- Never touch code spans, URLs, @mentions, emojis, or HTML/markdown markup.
|
|
39
|
-
- `original` MUST be an exact substring copied verbatim from the input text.
|
|
40
|
-
- Keep each edit minimal: a single word or short phrase, not a whole sentence.
|
|
41
|
-
|
|
42
|
-
Return ONLY valid JSON, no markdown, in exactly this shape:
|
|
43
|
-
{"edits":[{"original":"<verbatim>","suggestion":"<fixed>","reason":"spelling","confidence":0.0}]}
|
|
44
|
-
|
|
45
|
-
`confidence` is a float 0.0-1.0 for how sure you are it is a real typo.
|
|
46
|
-
If there are no typos, return {"edits":[]}.
|
|
47
|
-
PROMPT
|
|
48
|
-
|
|
49
|
-
def self.call
|
|
50
|
-
user = User.find_or_initialize_by(email: AGENT_EMAIL)
|
|
51
|
-
return user unless user.new_record?
|
|
52
|
-
|
|
53
|
-
user.assign_attributes(
|
|
54
|
-
name: "Typo Corrector",
|
|
55
|
-
password: SecureRandom.hex(32),
|
|
56
|
-
email_verified_at: Time.current,
|
|
57
|
-
searchable: false,
|
|
58
|
-
llm_vendor: ENV.fetch("COLLAVRE_DEFAULT_LLM_VENDOR", "gemini"),
|
|
59
|
-
llm_model: ENV.fetch("COLLAVRE_DEFAULT_LLM_MODEL", "gemini-3.1-flash-lite"),
|
|
60
|
-
system_prompt: SYSTEM_PROMPT,
|
|
61
|
-
tools: []
|
|
62
|
-
)
|
|
63
|
-
user.save!
|
|
64
|
-
Rails.logger.info "[Collavre] Typo Corrector agent ensured: #{AGENT_EMAIL}"
|
|
65
|
-
user
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
Collavre::TypoCorrectorSeed.call
|
|
20
|
+
Collavre::LlmModel.seed_default_suggestions!
|