collavre 0.3.0 → 0.3.2
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/app/controllers/collavre/admin/orchestration_controller.rb +1 -1
- data/app/controllers/collavre/comments_controller.rb +7 -1
- data/app/models/collavre/comment.rb +15 -15
- data/app/services/collavre/comments/topic_command.rb +30 -35
- data/app/services/collavre/orchestration/agent_orchestrator.rb +18 -4
- data/app/services/collavre/orchestration/policy_resolver.rb +1 -1
- data/app/services/collavre/orchestration/stuck_detector.rb +29 -1
- data/app/services/collavre/system_events/context_builder.rb +2 -7
- data/app/views/admin/shared/_tabs.html.erb +1 -1
- data/app/views/collavre/admin/orchestration/show.html.erb +1 -1
- data/config/locales/ai_agent.en.yml +1 -1
- data/config/locales/ai_agent.ko.yml +1 -1
- data/config/locales/comments.en.yml +2 -1
- data/config/locales/comments.ko.yml +2 -1
- data/config/locales/orchestration.en.yml +35 -0
- data/config/locales/orchestration.ko.yml +35 -0
- data/config/routes.rb +5 -0
- data/lib/collavre/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 726c316625f342dc6d95769cc629900ea58c58a830d5a072fa9bd86609724e1f
|
|
4
|
+
data.tar.gz: 629f17bdbc23f81f22c16a19b09ee6eaeab5013fc7e6de7096f5bcb6ff1ab3f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26eba94cd79976a2d21587c18e20b12734021fc7e3c207e0bd76c62cf3e2e1b8c258c711cc9f95b07440a0947702a58f1628e5df457fea2921255606e2ec0230
|
|
7
|
+
data.tar.gz: 547794c0208675a2a951320549ac26a07a01ea707ee1611a8d7a2dcf8d2f3475068b7e316d7de59037dfee0b03d1389b8671a191ee1c02e1798caed77e8d3e65
|
|
@@ -17,7 +17,7 @@ module Collavre
|
|
|
17
17
|
validate_policies!(parsed)
|
|
18
18
|
apply_policies!(parsed)
|
|
19
19
|
|
|
20
|
-
redirect_to
|
|
20
|
+
redirect_to admin_orchestration_path, notice: t("admin.orchestration.updated")
|
|
21
21
|
rescue Psych::SyntaxError => e
|
|
22
22
|
flash.now[:alert] = t("admin.orchestration.yaml_syntax_error", message: e.message)
|
|
23
23
|
@policies_yaml = yaml_content
|
|
@@ -179,7 +179,7 @@ module Collavre
|
|
|
179
179
|
chat: {
|
|
180
180
|
content: @comment.content
|
|
181
181
|
}
|
|
182
|
-
}) unless @comment.private?
|
|
182
|
+
}) unless @comment.private? || response.present?
|
|
183
183
|
@comment = Comment.with_attached_images.includes(:comment_reactions).find(@comment.id)
|
|
184
184
|
render partial: "collavre/comments/comment", locals: { comment: @comment, current_topic_id: current_topic_context }, status: :created
|
|
185
185
|
else
|
|
@@ -491,6 +491,12 @@ module Collavre
|
|
|
491
491
|
aliases: [ "/cal" ],
|
|
492
492
|
description: I18n.t("collavre.comments.command_menu.calendar_description"),
|
|
493
493
|
args: I18n.t("collavre.comments.command_menu.calendar_args")
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: "topic",
|
|
497
|
+
label: "/topic",
|
|
498
|
+
description: I18n.t("collavre.comments.command_menu.topic_description"),
|
|
499
|
+
args: I18n.t("collavre.comments.command_menu.topic_args")
|
|
494
500
|
}
|
|
495
501
|
] + mcp_command_items
|
|
496
502
|
end
|
|
@@ -75,10 +75,24 @@ module Collavre
|
|
|
75
75
|
:ok
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
def mentioned_users
|
|
79
|
+
return Collavre.user_class.none unless user
|
|
80
|
+
emails = mentioned_emails - [ user.email.downcase ]
|
|
81
|
+
names = mentioned_names - [ user.name.downcase ]
|
|
82
|
+
|
|
83
|
+
origin = creative.effective_origin
|
|
84
|
+
mentionable_users = Collavre.user_class.mentionable_for(origin)
|
|
85
|
+
|
|
86
|
+
scope = Collavre.user_class.none
|
|
87
|
+
scope = scope.or(mentionable_users.where(email: emails)) if emails.any?
|
|
88
|
+
scope = scope.or(mentionable_users.where("LOWER(name) IN (?)", names)) if names.any?
|
|
89
|
+
scope
|
|
90
|
+
end
|
|
91
|
+
|
|
78
92
|
private
|
|
79
93
|
|
|
80
94
|
def cancel_pending_tasks
|
|
81
|
-
Task.where(status: %w[pending running]).each do |task|
|
|
95
|
+
Task.where(status: %w[pending running queued]).each do |task|
|
|
82
96
|
if task.trigger_event_payload&.dig("comment", "id") == id
|
|
83
97
|
task.update!(status: "cancelled")
|
|
84
98
|
end
|
|
@@ -126,20 +140,6 @@ module Collavre
|
|
|
126
140
|
.uniq
|
|
127
141
|
end
|
|
128
142
|
|
|
129
|
-
def mentioned_users
|
|
130
|
-
return Collavre.user_class.none unless user
|
|
131
|
-
emails = mentioned_emails - [ user.email.downcase ]
|
|
132
|
-
names = mentioned_names - [ user.name.downcase ]
|
|
133
|
-
|
|
134
|
-
origin = creative.effective_origin
|
|
135
|
-
mentionable_users = Collavre.user_class.mentionable_for(origin)
|
|
136
|
-
|
|
137
|
-
scope = Collavre.user_class.none
|
|
138
|
-
scope = scope.or(mentionable_users.where(email: emails)) if emails.any?
|
|
139
|
-
scope = scope.or(mentionable_users.where("LOWER(name) IN (?)", names)) if names.any?
|
|
140
|
-
scope
|
|
141
|
-
end
|
|
142
|
-
|
|
143
143
|
def broadcast_create
|
|
144
144
|
return if private?
|
|
145
145
|
broadcast_append_later_to([ creative, :comments ], target: "comments-list", partial: "collavre/comments/comment")
|
|
@@ -34,19 +34,12 @@ module Collavre
|
|
|
34
34
|
content = comment.content.to_s.strip
|
|
35
35
|
|
|
36
36
|
# Extract topic name in quotes
|
|
37
|
-
name_match = content.match(/[""]([^""]+)[""]|"([^"]+)"/)
|
|
37
|
+
name_match = content.match(/[\u201c\u201d""]([^"\u201c\u201d""]+)[\u201c\u201d""]|"([^"]+)"/)
|
|
38
38
|
topic_name = name_match ? (name_match[1] || name_match[2]) : nil
|
|
39
39
|
|
|
40
40
|
return if topic_name.blank?
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
agent_match = content.match(/@(\w+)/)
|
|
44
|
-
agent_name = agent_match ? agent_match[1] : nil
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
name: topic_name,
|
|
48
|
-
agent_name: agent_name
|
|
49
|
-
}
|
|
42
|
+
{ name: topic_name }
|
|
50
43
|
end
|
|
51
44
|
end
|
|
52
45
|
|
|
@@ -54,45 +47,47 @@ module Collavre
|
|
|
54
47
|
data = parsed_args
|
|
55
48
|
return I18n.t("collavre.comments.topic_command.missing_name") if data.blank?
|
|
56
49
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
creative: creative,
|
|
60
|
-
user: user,
|
|
61
|
-
name: data[:name]
|
|
62
|
-
)
|
|
50
|
+
# Find primary agent from @mentions using the same parsing as chat
|
|
51
|
+
primary_agent = comment.mentioned_users.find(&:ai_user?)
|
|
63
52
|
|
|
64
|
-
#
|
|
65
|
-
|
|
53
|
+
# Find existing topic or create new one
|
|
54
|
+
existing_topic = Topic.find_by(creative: creative, name: data[:name])
|
|
66
55
|
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
56
|
+
if existing_topic
|
|
57
|
+
if primary_agent
|
|
58
|
+
set_primary_agent(existing_topic, primary_agent)
|
|
59
|
+
I18n.t("collavre.comments.topic_command.updated_agent",
|
|
60
|
+
name: existing_topic.name,
|
|
61
|
+
agent: primary_agent.name)
|
|
62
|
+
else
|
|
63
|
+
I18n.t("collavre.comments.topic_command.already_exists",
|
|
64
|
+
name: existing_topic.name)
|
|
65
|
+
end
|
|
72
66
|
else
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
topic = Topic.create!(
|
|
68
|
+
creative: creative,
|
|
69
|
+
user: user,
|
|
70
|
+
name: data[:name]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if primary_agent
|
|
74
|
+
set_primary_agent(topic, primary_agent)
|
|
75
|
+
I18n.t("collavre.comments.topic_command.created_with_agent",
|
|
75
76
|
name: topic.name,
|
|
76
|
-
|
|
77
|
+
agent: primary_agent.name)
|
|
77
78
|
else
|
|
78
79
|
I18n.t("collavre.comments.topic_command.created", name: topic.name)
|
|
79
80
|
end
|
|
80
81
|
end
|
|
81
82
|
end
|
|
82
83
|
|
|
83
|
-
def find_agent(name)
|
|
84
|
-
# Find AI agent by name (case-insensitive)
|
|
85
|
-
user_class = Collavre.configuration.user_class_name.constantize
|
|
86
|
-
user_class.where.not(llm_vendor: [ nil, "" ])
|
|
87
|
-
.where("LOWER(name) = ?", name.downcase)
|
|
88
|
-
.first
|
|
89
|
-
end
|
|
90
|
-
|
|
91
84
|
def set_primary_agent(topic, agent)
|
|
92
|
-
OrchestratorPolicy.
|
|
85
|
+
policy = OrchestratorPolicy.find_or_initialize_by(
|
|
93
86
|
policy_type: "arbitration",
|
|
94
87
|
scope_type: "Topic",
|
|
95
|
-
scope_id: topic.id
|
|
88
|
+
scope_id: topic.id
|
|
89
|
+
)
|
|
90
|
+
policy.update!(
|
|
96
91
|
config: {
|
|
97
92
|
"strategy" => "primary_first",
|
|
98
93
|
"primary_agent_id" => agent.id
|
|
@@ -24,23 +24,37 @@ module Collavre
|
|
|
24
24
|
if updated > 0
|
|
25
25
|
task.reload
|
|
26
26
|
refresh_deferred_context!(task)
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
if task.status == "cancelled"
|
|
29
|
+
# refresh_deferred_context! cancelled this task (no eligible comment),
|
|
30
|
+
# try the next queued task for this topic.
|
|
31
|
+
dequeue_next_for_topic(topic_id)
|
|
32
|
+
else
|
|
33
|
+
AiAgentJob.perform_later(task)
|
|
34
|
+
end
|
|
28
35
|
end
|
|
29
36
|
end
|
|
30
37
|
|
|
31
38
|
# Refresh trigger_event_payload so the deferred agent sees the latest
|
|
32
39
|
# conversation state instead of the stale snapshot from enqueue time.
|
|
40
|
+
# Skips AI agent's own comments to prevent self-response loops.
|
|
41
|
+
# Cancels the task if no eligible comment remains.
|
|
33
42
|
def self.refresh_deferred_context!(task)
|
|
34
43
|
context = task.trigger_event_payload
|
|
35
44
|
creative_id = context&.dig("creative", "id")
|
|
36
45
|
return unless creative_id && context&.key?("topic")
|
|
37
46
|
|
|
38
47
|
topic_id = context.dig("topic", "id")
|
|
39
|
-
|
|
48
|
+
scope = Comment
|
|
40
49
|
.where(creative_id: creative_id, topic_id: topic_id, private: false)
|
|
50
|
+
.where.not(user_id: task.agent_id)
|
|
41
51
|
.order(created_at: :desc)
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
latest_comment = scope.first
|
|
53
|
+
|
|
54
|
+
unless latest_comment
|
|
55
|
+
task.update!(status: "cancelled")
|
|
56
|
+
return
|
|
57
|
+
end
|
|
44
58
|
|
|
45
59
|
context["comment"] = {
|
|
46
60
|
"id" => latest_comment.id,
|
|
@@ -43,7 +43,7 @@ module Collavre
|
|
|
43
43
|
"token_spike_window_minutes" => 10
|
|
44
44
|
},
|
|
45
45
|
"stuck_detection" => {
|
|
46
|
-
"enabled" =>
|
|
46
|
+
"enabled" => true,
|
|
47
47
|
"task_stuck_threshold_minutes" => 30, # Task running for > N minutes
|
|
48
48
|
"creative_stall_threshold_minutes" => 120, # Creative no progress for > N minutes
|
|
49
49
|
"create_system_comment" => true # Create system comment on escalation
|
|
@@ -24,7 +24,7 @@ module Collavre
|
|
|
24
24
|
@policy_resolver = policy_resolver || PolicyResolver.new({})
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
# Run detection and escalation
|
|
27
|
+
# Run detection, auto-recovery, and escalation
|
|
28
28
|
# Returns Result with stuck items and escalation count
|
|
29
29
|
def detect_and_escalate
|
|
30
30
|
config = stuck_detection_config
|
|
@@ -34,6 +34,7 @@ module Collavre
|
|
|
34
34
|
stuck_items.concat(detect_stuck_tasks(config))
|
|
35
35
|
stuck_items.concat(detect_stalled_creatives(config))
|
|
36
36
|
|
|
37
|
+
auto_recover_stuck_tasks(stuck_items)
|
|
37
38
|
escalated_count = escalate_stuck_items(stuck_items, config)
|
|
38
39
|
|
|
39
40
|
Result.new(stuck_items: stuck_items, escalated_count: escalated_count)
|
|
@@ -52,6 +53,33 @@ module Collavre
|
|
|
52
53
|
|
|
53
54
|
private
|
|
54
55
|
|
|
56
|
+
# Auto-recover stuck tasks by marking them as failed and draining the queue.
|
|
57
|
+
def auto_recover_stuck_tasks(stuck_items)
|
|
58
|
+
stuck_items.each do |stuck_item|
|
|
59
|
+
next unless stuck_item.type == :task
|
|
60
|
+
|
|
61
|
+
task = stuck_item.item
|
|
62
|
+
next unless task.status == "running"
|
|
63
|
+
|
|
64
|
+
task.update!(status: "failed")
|
|
65
|
+
Rails.logger.info(
|
|
66
|
+
"[StuckDetector] Auto-recovered task #{task.id} (agent=#{task.agent_id}): " \
|
|
67
|
+
"marked as failed after #{((Time.current - stuck_item.stuck_since) / 60).round} minutes"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Release resources held by the stuck task
|
|
71
|
+
if task.agent
|
|
72
|
+
tracker = ResourceTracker.for(task.agent)
|
|
73
|
+
tracker.release!(task.id)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Drain the queue for the topic so waiting tasks can execute
|
|
77
|
+
AgentOrchestrator.dequeue_next_for_topic(task.topic_id)
|
|
78
|
+
rescue StandardError => e
|
|
79
|
+
Rails.logger.error("[StuckDetector] Auto-recovery failed for task #{task.id}: #{e.message}")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
55
83
|
def stuck_detection_config
|
|
56
84
|
@policy_resolver.resolve("stuck_detection")
|
|
57
85
|
end
|
|
@@ -52,16 +52,11 @@ module Collavre
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def mentioned_user(chat_context)
|
|
55
|
-
# This mimics the chat.mentioned_user function requested
|
|
56
|
-
# It assumes chat_context has 'content' or similar, or we might need to look up the comment
|
|
57
|
-
# For now, let's assume the context passed in already has the necessary info or we extract it.
|
|
58
|
-
# If the event is comment_created, the payload usually has the comment content.
|
|
59
|
-
|
|
60
55
|
content = chat_context["content"]
|
|
61
56
|
return nil unless content
|
|
62
57
|
|
|
63
|
-
#
|
|
64
|
-
match = content.match(/\A@([^:]+?):\s*/)
|
|
58
|
+
# Canonical mention format: @name: (with colon)
|
|
59
|
+
match = content.match(/\A@([^:]+?):\s*/)
|
|
65
60
|
return nil unless match
|
|
66
61
|
|
|
67
62
|
name = match[1].strip
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="tab-list">
|
|
2
2
|
<%= link_to t('admin.tabs.system'), main_app.admin_path, class: "tab-button #{'active' if controller_name == 'settings'}" %>
|
|
3
3
|
<%= link_to t('admin.tabs.users'), collavre.users_path, class: "tab-button #{'active' if controller_name == 'users'}" %>
|
|
4
|
-
<%= link_to t('admin.tabs.orchestration'),
|
|
4
|
+
<%= link_to t('admin.tabs.orchestration'), collavre.admin_orchestration_path, class: "tab-button #{'active' if controller_name == 'orchestration'}" %>
|
|
5
5
|
</div>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
</p>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
|
-
<%= form_with url:
|
|
17
|
+
<%= form_with url: admin_orchestration_path, method: :patch, local: true, html: { class: 'profile-form' } do |f| %>
|
|
18
18
|
<div>
|
|
19
19
|
<label for="policies_yaml" style="font-weight: 600; display: block; margin-bottom: 0.5em;">
|
|
20
20
|
<%= t('admin.orchestration.policies_yaml') %>
|
|
@@ -30,7 +30,7 @@ en:
|
|
|
30
30
|
reviewer_header: "### Reviewers (request review)"
|
|
31
31
|
reference_header: "### References (information only)"
|
|
32
32
|
rules_header: "## Collaboration Rules"
|
|
33
|
-
mention_rule: "- Call other agents: @name request"
|
|
33
|
+
mention_rule: "- Call other agents: @name: request"
|
|
34
34
|
confidence_rule: "- Re-evaluate before responding if uncertain"
|
|
35
35
|
escalation_rule: "- Ask escalation targets for help when stuck"
|
|
36
36
|
review_rule: "- Request review from reviewers when code review is needed"
|
|
@@ -30,7 +30,7 @@ ko:
|
|
|
30
30
|
reviewer_header: "### 리뷰어 (검토 요청)"
|
|
31
31
|
reference_header: "### 참조 (정보 요청만)"
|
|
32
32
|
rules_header: "## 협업 규칙"
|
|
33
|
-
mention_rule: "- 다른 Agent 호출:
|
|
33
|
+
mention_rule: "- 다른 Agent 호출: @이름: 요청내용"
|
|
34
34
|
confidence_rule: "- 확신이 낮으면 재검토 후 발화"
|
|
35
35
|
escalation_rule: "- 막히면 에스컬레이션 대상에게 도움 요청"
|
|
36
36
|
review_rule: "- 코드 리뷰가 필요하면 리뷰어에게 요청"
|
|
@@ -91,7 +91,8 @@ en:
|
|
|
91
91
|
missing_name: 'Please specify a topic name in quotes: /topic "topic name"'
|
|
92
92
|
created: 'Topic "%{name}" created.'
|
|
93
93
|
created_with_agent: 'Topic "%{name}" created with @%{agent} as primary agent.'
|
|
94
|
-
|
|
94
|
+
updated_agent: 'Topic "%{name}" primary agent updated to @%{agent}.'
|
|
95
|
+
already_exists: 'Topic "%{name}" already exists.'
|
|
95
96
|
read_by: Read by %{name}
|
|
96
97
|
activity_logs_summary: Activity Logs
|
|
97
98
|
calendar_events:
|
|
@@ -88,7 +88,8 @@ ko:
|
|
|
88
88
|
missing_name: '토픽 이름을 따옴표로 지정하세요: /topic "토픽 이름"'
|
|
89
89
|
created: '토픽 "%{name}"이(가) 생성되었습니다.'
|
|
90
90
|
created_with_agent: '토픽 "%{name}"이(가) 생성되었습니다. @%{agent}이(가) Primary Agent로 설정되었습니다.'
|
|
91
|
-
|
|
91
|
+
updated_agent: '토픽 "%{name}"의 Primary Agent가 @%{agent}(으)로 변경되었습니다.'
|
|
92
|
+
already_exists: '토픽 "%{name}"이(가) 이미 존재합니다.'
|
|
92
93
|
read_by: "%{name} 님이 읽음"
|
|
93
94
|
activity_logs_summary: 활동 기록
|
|
94
95
|
calendar_events:
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
en:
|
|
2
|
+
admin:
|
|
3
|
+
orchestration:
|
|
4
|
+
title: "AI Agent Orchestration"
|
|
5
|
+
description: "Configure how AI agents are selected and scheduled to respond. Edit the YAML below to customize policies."
|
|
6
|
+
policies_yaml: "Policies (YAML)"
|
|
7
|
+
yaml_hint: "Define arbitration and scheduling policies. Global policies apply everywhere; overrides apply to specific scopes."
|
|
8
|
+
reference: "Quick Reference"
|
|
9
|
+
arbitration_strategies: "Arbitration Strategies"
|
|
10
|
+
strategy_all: "All matched agents respond"
|
|
11
|
+
strategy_primary_first: "Primary agent responds first, others only if unavailable"
|
|
12
|
+
strategy_round_robin: "Rotate between agents for each message"
|
|
13
|
+
strategy_bid: "Select agent based on relevance score"
|
|
14
|
+
scheduling_options: "Scheduling Options"
|
|
15
|
+
opt_max_concurrent: "Maximum simultaneous jobs per agent"
|
|
16
|
+
opt_daily_token: "Daily token usage limit"
|
|
17
|
+
opt_rate_limit: "Requests allowed per minute"
|
|
18
|
+
opt_backoff: "Delay strategy when busy (immediate/linear/exponential)"
|
|
19
|
+
opt_topic_max_concurrent: "Maximum simultaneous jobs per topic (default: 1)"
|
|
20
|
+
scope_types: "Override Scopes"
|
|
21
|
+
scope_creative: "Apply to a specific workspace"
|
|
22
|
+
scope_topic: "Apply to a specific conversation"
|
|
23
|
+
scope_user: "Apply to a specific AI agent"
|
|
24
|
+
save: "Save Policies"
|
|
25
|
+
updated: "Orchestration policies updated successfully."
|
|
26
|
+
yaml_syntax_error: "YAML syntax error: %{message}"
|
|
27
|
+
invalid_format: "Invalid format: expected a YAML object with arbitration/scheduling keys."
|
|
28
|
+
unknown_policy_type: "Unknown policy type: %{type}. Use 'arbitration' or 'scheduling'."
|
|
29
|
+
invalid_policy_structure: "Invalid structure for '%{type}': expected an object with 'global' and/or 'overrides'."
|
|
30
|
+
invalid_global_config: "Invalid global config for '%{type}': expected an object."
|
|
31
|
+
invalid_overrides: "Invalid overrides for '%{type}': expected an array."
|
|
32
|
+
invalid_override_format: "Invalid override at '%{type}' index %{index}: expected an object."
|
|
33
|
+
invalid_scope_type: "Invalid scope_type at '%{type}' index %{index}: '%{scope_type}'. Use Creative, Topic, or User."
|
|
34
|
+
invalid_scope_id: "Invalid scope_id at '%{type}' index %{index}: must be a positive integer."
|
|
35
|
+
invalid_override_config: "Invalid config at '%{type}' index %{index}: expected an object."
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
ko:
|
|
2
|
+
admin:
|
|
3
|
+
orchestration:
|
|
4
|
+
title: "AI 에이전트 오케스트레이션"
|
|
5
|
+
description: "AI 에이전트가 응답하도록 선택되고 스케줄링되는 방식을 설정합니다. 아래 YAML을 수정하여 정책을 커스터마이즈하세요."
|
|
6
|
+
policies_yaml: "정책 (YAML)"
|
|
7
|
+
yaml_hint: "중재(arbitration) 및 스케줄링 정책을 정의합니다. 전역 정책은 모든 곳에 적용되고, 오버라이드는 특정 범위에 적용됩니다."
|
|
8
|
+
reference: "빠른 참조"
|
|
9
|
+
arbitration_strategies: "중재 전략"
|
|
10
|
+
strategy_all: "매칭된 모든 에이전트가 응답"
|
|
11
|
+
strategy_primary_first: "기본 에이전트가 먼저 응답, 불가능할 때만 다른 에이전트"
|
|
12
|
+
strategy_round_robin: "메시지마다 에이전트를 순환"
|
|
13
|
+
strategy_bid: "관련성 점수에 따라 에이전트 선택"
|
|
14
|
+
scheduling_options: "스케줄링 옵션"
|
|
15
|
+
opt_max_concurrent: "에이전트당 최대 동시 작업 수"
|
|
16
|
+
opt_daily_token: "일일 토큰 사용량 한도"
|
|
17
|
+
opt_rate_limit: "분당 허용 요청 수"
|
|
18
|
+
opt_backoff: "바쁠 때 지연 전략 (immediate/linear/exponential)"
|
|
19
|
+
opt_topic_max_concurrent: "토픽당 최대 동시 작업 수 (기본값: 1)"
|
|
20
|
+
scope_types: "오버라이드 범위"
|
|
21
|
+
scope_creative: "특정 워크스페이스에 적용"
|
|
22
|
+
scope_topic: "특정 대화에 적용"
|
|
23
|
+
scope_user: "특정 AI 에이전트에 적용"
|
|
24
|
+
save: "정책 저장"
|
|
25
|
+
updated: "오케스트레이션 정책이 성공적으로 업데이트되었습니다."
|
|
26
|
+
yaml_syntax_error: "YAML 문법 오류: %{message}"
|
|
27
|
+
invalid_format: "잘못된 형식: arbitration/scheduling 키를 가진 YAML 객체가 필요합니다."
|
|
28
|
+
unknown_policy_type: "알 수 없는 정책 유형: %{type}. 'arbitration' 또는 'scheduling'을 사용하세요."
|
|
29
|
+
invalid_policy_structure: "'%{type}'의 구조가 잘못되었습니다: 'global' 및/또는 'overrides'를 가진 객체가 필요합니다."
|
|
30
|
+
invalid_global_config: "'%{type}'의 전역 설정이 잘못되었습니다: 객체가 필요합니다."
|
|
31
|
+
invalid_overrides: "'%{type}'의 오버라이드가 잘못되었습니다: 배열이 필요합니다."
|
|
32
|
+
invalid_override_format: "'%{type}' 인덱스 %{index}의 오버라이드가 잘못되었습니다: 객체가 필요합니다."
|
|
33
|
+
invalid_scope_type: "'%{type}' 인덱스 %{index}의 scope_type이 잘못되었습니다: '%{scope_type}'. Creative, Topic, 또는 User를 사용하세요."
|
|
34
|
+
invalid_scope_id: "'%{type}' 인덱스 %{index}의 scope_id가 잘못되었습니다: 양의 정수여야 합니다."
|
|
35
|
+
invalid_override_config: "'%{type}' 인덱스 %{index}의 config가 잘못되었습니다: 객체가 필요합니다."
|
data/config/routes.rb
CHANGED
|
@@ -93,4 +93,9 @@ Collavre::Engine.routes.draw do
|
|
|
93
93
|
|
|
94
94
|
post "/creative_expanded_states/toggle", to: "creative_expanded_states#toggle"
|
|
95
95
|
post "/comment_read_pointers/update", to: "comment_read_pointers#update"
|
|
96
|
+
|
|
97
|
+
# Admin orchestration
|
|
98
|
+
scope "/admin", as: :admin do
|
|
99
|
+
resource :orchestration, only: [ :show, :update ], controller: "admin/orchestration"
|
|
100
|
+
end
|
|
96
101
|
end
|
data/lib/collavre/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: collavre
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Collavre
|
|
@@ -472,6 +472,8 @@ files:
|
|
|
472
472
|
- config/locales/invites.ko.yml
|
|
473
473
|
- config/locales/notifications.en.yml
|
|
474
474
|
- config/locales/notifications.ko.yml
|
|
475
|
+
- config/locales/orchestration.en.yml
|
|
476
|
+
- config/locales/orchestration.ko.yml
|
|
475
477
|
- config/locales/plans.en.yml
|
|
476
478
|
- config/locales/plans.ko.yml
|
|
477
479
|
- config/locales/themes.en.yml
|