aia 1.1.0 → 2.0.0.0.pre.alpha
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/.envrc +5 -1
- data/.loki +231 -0
- data/.quality/flay_baseline.txt +1 -0
- data/.quality/flog_baseline.txt +29 -0
- data/.quality/reek_baseline.txt +80 -0
- data/.rubocop.yml +116 -0
- data/.version +1 -1
- data/CHANGELOG.md +266 -42
- data/IMPLEMENTATION_PLAN.md +506 -0
- data/README.md +266 -238
- data/Rakefile +118 -5
- data/architecture_review.md +314 -0
- data/bin/aia +16 -0
- data/docs/AGENTS.md +40 -0
- data/docs/advanced-prompting.md +67 -3
- data/docs/cli-reference.md +312 -56
- data/docs/configuration.md +130 -19
- data/docs/contributing.md +56 -2
- data/docs/directives-reference.md +593 -78
- data/docs/faq.md +85 -3
- data/docs/guides/available-models.md +1 -1
- data/docs/guides/basic-usage.md +6 -6
- data/docs/guides/chat.md +40 -16
- data/docs/guides/crew.md +239 -0
- data/docs/guides/executable-prompts.md +1 -1
- data/docs/guides/index.md +1 -0
- data/docs/guides/models.md +15 -0
- data/docs/index.md +29 -2
- data/docs/installation.md +44 -17
- data/docs/mcp-integration.md +40 -0
- data/docs/prompt_management.md +85 -86
- data/docs/security.md +47 -0
- data/docs/special_projects_guide.md +386 -0
- data/docs/tools-and-mcp-examples.md +23 -0
- data/docs/workflows-and-pipelines.md +84 -7
- data/examples/.gitignore +1 -0
- data/examples/00_setup_aia.sh +27 -44
- data/examples/11_multi_model.sh +4 -14
- data/examples/12_token_usage.sh +3 -12
- data/examples/18_tools.sh +10 -2
- data/examples/22_chat_mode.sh +0 -10
- data/examples/23_verify.sh +139 -0
- data/examples/24_decompose.sh +139 -0
- data/examples/25_spawn.sh +139 -0
- data/examples/26_debate.sh +97 -0
- data/examples/27_mention_routing.sh +157 -0
- data/examples/28_model_switching.sh +106 -0
- data/examples/29_agent_harness.sh +177 -0
- data/examples/README.md +65 -0
- data/examples/advanced_multi_robot_capabilities_without_examples.md +106 -0
- data/examples/aia_config.yml +1 -1
- data/examples/aia_config_orchestrator.yml +45 -0
- data/examples/common.sh +19 -0
- data/examples/context/tech_stack.md +2 -2
- data/examples/prompts_dir/project_summary +2 -2
- data/examples/prompts_dir/roles/orchestrator.md +21 -0
- data/examples/requirements/sinatra_taskflow_app.md +139 -0
- data/examples/rules/01_classify_ruby.rb +16 -0
- data/examples/rules/02_prefer_claude_for_code.rb +19 -0
- data/examples/rules/03_gate_prompt_length.rb +19 -0
- data/examples/rules/04_tool_selection.rb +41 -0
- data/examples/rules/README.md +30 -0
- data/examples/run_all.sh +48 -15
- data/examples/tools/word_count_tool.rb +1 -1
- data/lib/AGENTS.md +57 -0
- data/lib/aia/chat_loop.rb +306 -159
- data/lib/aia/config/cli_parser.rb +174 -111
- data/lib/aia/config/defaults.yml +62 -33
- data/lib/aia/config/mcp_parser.rb +39 -46
- data/lib/aia/config/model_spec.rb +34 -2
- data/lib/aia/config/validator.rb +121 -138
- data/lib/aia/config.rb +110 -145
- data/lib/aia/content_extractor.rb +153 -0
- data/lib/aia/cost_calculator.rb +38 -0
- data/lib/aia/crew.rb +164 -0
- data/lib/aia/debate_handler.rb +166 -0
- data/lib/aia/delegate_handler.rb +112 -0
- data/lib/aia/directive.rb +33 -18
- data/lib/aia/directive_processor.rb +16 -7
- data/lib/aia/directives/configuration_directives.rb +160 -20
- data/lib/aia/directives/context_directives.rb +38 -26
- data/lib/aia/directives/execution_directives.rb +136 -4
- data/lib/aia/directives/model_directives.rb +76 -34
- data/lib/aia/directives/trakflow_directives.rb +44 -0
- data/lib/aia/directives/utility_directives.rb +203 -6
- data/lib/aia/directives/web_and_file_directives.rb +96 -60
- data/lib/aia/errors.rb +15 -0
- data/lib/aia/fact_asserter.rb +27 -0
- data/lib/aia/fzf.rb +9 -31
- data/lib/aia/handler_context.rb +17 -0
- data/lib/aia/handler_protocol.rb +19 -0
- data/lib/aia/history_transfer.rb +55 -0
- data/lib/aia/input_collector.rb +3 -3
- data/lib/aia/layered_orchestrator.rb +448 -0
- data/lib/aia/logger.rb +24 -4
- data/lib/aia/mcp_config_normalizer.rb +35 -0
- data/lib/aia/mcp_connection_manager.rb +305 -0
- data/lib/aia/mcp_discovery.rb +44 -0
- data/lib/aia/mcp_grouper.rb +33 -0
- data/lib/aia/mcp_utility.rb +57 -0
- data/lib/aia/mention_router.rb +260 -0
- data/lib/aia/model_alias_registry.rb +97 -0
- data/lib/aia/model_switch_handler.rb +100 -0
- data/lib/aia/network_builder.rb +155 -0
- data/lib/aia/network_memory_manager.rb +55 -0
- data/lib/aia/patches/ruby_llm_streaming_error.rb +43 -0
- data/lib/aia/patches/ruby_llm_tool_error.rb +96 -0
- data/lib/aia/pipeline_orchestrator.rb +262 -0
- data/lib/aia/plugin_loader.rb +170 -0
- data/lib/aia/plugin_monitor.rb +208 -0
- data/lib/aia/prompt_decomposer.rb +157 -0
- data/lib/aia/prompt_handler.rb +19 -39
- data/lib/aia/robot_builder.rb +51 -0
- data/lib/aia/robot_factory.rb +334 -0
- data/lib/aia/robot_namer.rb +116 -0
- data/lib/aia/session.rb +83 -17
- data/lib/aia/session_tracker.rb +209 -0
- data/lib/aia/similarity_scorer.rb +39 -0
- data/lib/aia/skill_utils.rb +105 -1
- data/lib/aia/spawn_handler.rb +129 -0
- data/lib/aia/spawn_spec_parser.rb +65 -0
- data/lib/aia/special_mode_handler.rb +302 -0
- data/lib/aia/startup_coordinator.rb +150 -0
- data/lib/aia/streaming_runner.rb +169 -0
- data/lib/aia/system_prompt_assembler.rb +88 -0
- data/lib/aia/task_coordinator.rb +202 -0
- data/lib/aia/task_decomposer.rb +57 -0
- data/lib/aia/task_executor.rb +51 -0
- data/lib/aia/tfidf_math.rb +27 -0
- data/lib/aia/tool_filter/tfidf.rb +116 -0
- data/lib/aia/tool_filter/wordnet_expander.rb +127 -0
- data/lib/aia/tool_filter.rb +82 -0
- data/lib/aia/tool_filter_registry.rb +30 -0
- data/lib/aia/tool_filter_strategy.rb +143 -0
- data/lib/aia/tool_loader.rb +210 -0
- data/lib/aia/tool_utility.rb +30 -0
- data/lib/aia/tools/delegate_to_foreman_tool.rb +70 -0
- data/lib/aia/tools/recruit_robot_tool.rb +60 -0
- data/lib/aia/tools/reskill_robot_tool.rb +44 -0
- data/lib/aia/tools/task_board_tool.rb +114 -0
- data/lib/aia/trakflow_bridge.rb +173 -0
- data/lib/aia/turn_state.rb +94 -0
- data/lib/aia/ui_presenter.rb +166 -198
- data/lib/aia/utility.rb +134 -87
- data/lib/aia/{history_manager.rb → variable_input_collector.rb} +8 -9
- data/lib/aia/verification_network.rb +58 -0
- data/lib/aia.rb +108 -63
- data/mkdocs.yml +1 -0
- metadata +179 -56
- data/justfile +0 -215
- data/lib/aia/adapter/chat_execution.rb +0 -242
- data/lib/aia/adapter/error_handler.rb +0 -68
- data/lib/aia/adapter/gem_activator.rb +0 -57
- data/lib/aia/adapter/mcp_connector.rb +0 -274
- data/lib/aia/adapter/modality_handlers.rb +0 -167
- data/lib/aia/adapter/model_registry.rb +0 -81
- data/lib/aia/adapter/multi_model_chat.rb +0 -218
- data/lib/aia/adapter/provider_configurator.rb +0 -59
- data/lib/aia/adapter/tool_filter.rb +0 -85
- data/lib/aia/adapter/tool_loader.rb +0 -90
- data/lib/aia/chat_processor_service.rb +0 -164
- data/lib/aia/prompt_pipeline.rb +0 -183
- data/lib/aia/ruby_llm_adapter.rb +0 -95
- data/lib/extensions/openstruct_merge.rb +0 -48
- data/lib/extensions/ruby_llm/.irbrc +0 -56
- data/lib/extensions/ruby_llm/modalities.rb +0 -36
- data/lib/extensions/ruby_llm/provider_fix.rb +0 -79
- data/lib/refinements/string.rb +0 -16
- data/main.just +0 -76
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# lib/aia/tools/task_board_tool.rb
|
|
4
|
+
#
|
|
5
|
+
# RubyLLM::Tool that lets robots manage a shared TrakFlow task board.
|
|
6
|
+
# Robots can create tasks for each other, check what's ready, claim
|
|
7
|
+
# work, and report completion — all through standard tool-use protocol.
|
|
8
|
+
|
|
9
|
+
require "json"
|
|
10
|
+
|
|
11
|
+
module AIA
|
|
12
|
+
class TaskBoardTool < RubyLLM::Tool
|
|
13
|
+
description "Manage a shared task board. Create tasks for other robots, " \
|
|
14
|
+
"check what's ready, claim tasks, and report completion."
|
|
15
|
+
|
|
16
|
+
param :action, type: :string, required: true,
|
|
17
|
+
desc: "One of: create, plan, ready, claim, complete, block, status"
|
|
18
|
+
param :title, type: :string,
|
|
19
|
+
desc: "Task title (for create/plan actions)"
|
|
20
|
+
param :assignee, type: :string,
|
|
21
|
+
desc: "Robot name to assign to (for create/claim/ready)"
|
|
22
|
+
param :labels, type: :string,
|
|
23
|
+
desc: "Comma-separated labels (for create)"
|
|
24
|
+
param :blocked_by, type: :string,
|
|
25
|
+
desc: "Comma-separated blocker task IDs (for create)"
|
|
26
|
+
param :steps, type: :string,
|
|
27
|
+
desc: "JSON array of step objects [{title:, assignee:}] (for plan)"
|
|
28
|
+
param :task_id, type: :string,
|
|
29
|
+
desc: "Task ID (for claim/complete/block)"
|
|
30
|
+
param :result, type: :string,
|
|
31
|
+
desc: "Result summary (for complete) or reason (for block)"
|
|
32
|
+
|
|
33
|
+
# Never auto-discovered onto primary robots. Only Foreman
|
|
34
|
+
# (built by DelegateToForemanTool) receives this tool explicitly.
|
|
35
|
+
def available?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def execute(action:, **params)
|
|
40
|
+
coordinator = AIA.task_coordinator
|
|
41
|
+
return "Task board unavailable (TrakFlow not initialized)" unless coordinator&.available?
|
|
42
|
+
|
|
43
|
+
robot_name = params[:_robot_name] || "unknown"
|
|
44
|
+
|
|
45
|
+
case action
|
|
46
|
+
when "create" then execute_create(coordinator, robot_name, params)
|
|
47
|
+
when "plan" then execute_plan(coordinator, robot_name, params)
|
|
48
|
+
when "ready" then execute_ready(coordinator, params)
|
|
49
|
+
when "claim" then execute_claim(coordinator, robot_name, params)
|
|
50
|
+
when "complete" then execute_complete(coordinator, robot_name, params)
|
|
51
|
+
when "block" then execute_block(coordinator, robot_name, params)
|
|
52
|
+
when "status" then coordinator.status_summary || "No tasks"
|
|
53
|
+
else
|
|
54
|
+
"Unknown action: #{action}. Use: create, plan, ready, claim, complete, block, status"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def execute_create(coordinator, robot_name, params)
|
|
61
|
+
task = coordinator.create_task(
|
|
62
|
+
params[:title],
|
|
63
|
+
assignee: params[:assignee],
|
|
64
|
+
labels: parse_csv(params[:labels]),
|
|
65
|
+
blocked_by: parse_csv(params[:blocked_by]),
|
|
66
|
+
creator: robot_name
|
|
67
|
+
)
|
|
68
|
+
task ? "Created [#{task.id}] '#{task.title}'" : "Failed to create task"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def execute_plan(coordinator, robot_name, params)
|
|
72
|
+
steps = JSON.parse(params[:steps] || "[]", symbolize_names: true)
|
|
73
|
+
result = coordinator.create_plan(
|
|
74
|
+
params[:title], steps: steps, creator: robot_name
|
|
75
|
+
)
|
|
76
|
+
result ? "Plan created with #{result[:steps].size} steps" : "Failed to create plan"
|
|
77
|
+
rescue JSON::ParserError => e
|
|
78
|
+
"Invalid steps JSON: #{e.message}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def execute_ready(coordinator, params)
|
|
82
|
+
tasks = coordinator.ready_tasks(robot_name: params[:assignee])
|
|
83
|
+
return "No ready tasks" if tasks.empty?
|
|
84
|
+
tasks.map { |t| "[#{t.id}] #{t.title} (#{t.assignee || 'unassigned'})" }.join("\n")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def execute_claim(coordinator, robot_name, params)
|
|
88
|
+
coordinator.claim_task(params[:task_id], robot_name)
|
|
89
|
+
"Claimed task #{params[:task_id]}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def execute_complete(coordinator, robot_name, params)
|
|
93
|
+
coordinator.complete_task(
|
|
94
|
+
params[:task_id],
|
|
95
|
+
result: params[:result] || "Done",
|
|
96
|
+
robot_name: robot_name
|
|
97
|
+
)
|
|
98
|
+
"Completed task #{params[:task_id]}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def execute_block(coordinator, robot_name, params)
|
|
102
|
+
coordinator.block_task(
|
|
103
|
+
params[:task_id],
|
|
104
|
+
reason: params[:result] || "Blocked",
|
|
105
|
+
robot_name: robot_name
|
|
106
|
+
)
|
|
107
|
+
"Blocked task #{params[:task_id]}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def parse_csv(value)
|
|
111
|
+
(value || "").split(",").map(&:strip).reject(&:empty?)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# lib/aia/trakflow_bridge.rb
|
|
4
|
+
#
|
|
5
|
+
# Bridge between AIA and TrakFlow task tracking.
|
|
6
|
+
# Uses the trak_flow gem directly as a Ruby library —
|
|
7
|
+
# no MCP server or LLM intermediation needed.
|
|
8
|
+
|
|
9
|
+
require "trak_flow"
|
|
10
|
+
|
|
11
|
+
module AIA
|
|
12
|
+
class TrakFlowBridge
|
|
13
|
+
def initialize
|
|
14
|
+
@db = connect_database
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Whether a TrakFlow project is initialized and the database is accessible.
|
|
18
|
+
#
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
def available?
|
|
21
|
+
!@db.nil?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Create a TrakFlow plan from a pipeline of prompt IDs.
|
|
25
|
+
# Each step blocks the next, enforcing sequential execution.
|
|
26
|
+
#
|
|
27
|
+
# @param pipeline [Array<String>] prompt IDs in pipeline order
|
|
28
|
+
# @return [String, nil] summary of created plan
|
|
29
|
+
def create_plan_from_pipeline(pipeline)
|
|
30
|
+
return nil unless available?
|
|
31
|
+
|
|
32
|
+
plan_title = "Pipeline: #{pipeline.join(' → ')}"
|
|
33
|
+
plan = TrakFlow::Models::Task.new(title: plan_title, plan: true, type: "task")
|
|
34
|
+
plan = @db.create_task(plan)
|
|
35
|
+
|
|
36
|
+
prev_step = nil
|
|
37
|
+
pipeline.each_with_index do |prompt_id, i|
|
|
38
|
+
step = @db.create_child_task(plan.id, {
|
|
39
|
+
title: "Step #{i + 1}: #{prompt_id}",
|
|
40
|
+
type: "task"
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
if prev_step
|
|
44
|
+
@db.add_dependency(TrakFlow::Models::Dependency.new(
|
|
45
|
+
source_id: prev_step.id,
|
|
46
|
+
target_id: step.id,
|
|
47
|
+
type: "blocks"
|
|
48
|
+
))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
prev_step = step
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
"Plan '#{plan_title}' created with #{pipeline.size} steps."
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
$stderr.puts "Warning: TrakFlow plan creation failed: #{e.message}"
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Update a task's status in TrakFlow.
|
|
61
|
+
#
|
|
62
|
+
# @param step_name [String] task title to find
|
|
63
|
+
# @param status [Symbol] :started, :completed, or :failed
|
|
64
|
+
# @param reason [String, nil] reason for failure
|
|
65
|
+
def update_step_status(step_name, status, reason: nil)
|
|
66
|
+
return unless available?
|
|
67
|
+
|
|
68
|
+
task = find_task_by_title(step_name)
|
|
69
|
+
return unless task
|
|
70
|
+
|
|
71
|
+
case status
|
|
72
|
+
when :started
|
|
73
|
+
task.status = "in_progress"
|
|
74
|
+
@db.update_task(task)
|
|
75
|
+
when :completed
|
|
76
|
+
task.close!(reason: reason || "Completed")
|
|
77
|
+
@db.update_task(task)
|
|
78
|
+
when :failed
|
|
79
|
+
task.status = "blocked"
|
|
80
|
+
task.append_trace("blocked", reason) if reason
|
|
81
|
+
@db.update_task(task)
|
|
82
|
+
end
|
|
83
|
+
rescue StandardError => e
|
|
84
|
+
$stderr.puts "Warning: TrakFlow status update failed: #{e.message}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# List ready tasks (tasks with no open blockers).
|
|
88
|
+
#
|
|
89
|
+
# @return [String, nil] formatted list of ready tasks
|
|
90
|
+
def check_ready_tasks
|
|
91
|
+
return nil unless available?
|
|
92
|
+
|
|
93
|
+
tasks = @db.ready_tasks
|
|
94
|
+
return nil if tasks.empty?
|
|
95
|
+
|
|
96
|
+
lines = tasks.map { |t| " - [#{t.id}] #{t.title} (#{t.status})" }
|
|
97
|
+
"Ready tasks (#{tasks.size}):\n#{lines.join("\n")}"
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
$stderr.puts "Warning: TrakFlow ready tasks query failed: #{e.message}"
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get a summary of the current TrakFlow project state.
|
|
104
|
+
#
|
|
105
|
+
# @return [String, nil] project summary
|
|
106
|
+
def project_summary
|
|
107
|
+
return nil unless available?
|
|
108
|
+
|
|
109
|
+
all = @db.list_tasks({})
|
|
110
|
+
by_status = all.group_by(&:status)
|
|
111
|
+
|
|
112
|
+
lines = ["TrakFlow Project Summary (#{all.size} tasks):"]
|
|
113
|
+
by_status.sort.each do |status, tasks|
|
|
114
|
+
lines << " #{status}: #{tasks.size}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
ready = @db.ready_tasks
|
|
118
|
+
lines << " ready (unblocked): #{ready.size}" unless ready.empty?
|
|
119
|
+
|
|
120
|
+
lines.join("\n")
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
$stderr.puts "Warning: TrakFlow summary failed: #{e.message}"
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Create a task in TrakFlow.
|
|
127
|
+
#
|
|
128
|
+
# @param title [String] task title
|
|
129
|
+
# @param description [String, nil] task description
|
|
130
|
+
# @param labels [Array<String>] labels to attach
|
|
131
|
+
# @return [String, nil] confirmation message
|
|
132
|
+
def create_task(title, description: nil, labels: [])
|
|
133
|
+
return nil unless available?
|
|
134
|
+
|
|
135
|
+
task = TrakFlow::Models::Task.new(
|
|
136
|
+
title: title,
|
|
137
|
+
description: description,
|
|
138
|
+
type: "task"
|
|
139
|
+
)
|
|
140
|
+
task = @db.create_task(task)
|
|
141
|
+
|
|
142
|
+
labels.each do |label_name|
|
|
143
|
+
@db.add_label(TrakFlow::Models::Label.new(task_id: task.id, name: label_name))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
"Task created: [#{task.id}] #{title}"
|
|
147
|
+
rescue StandardError => e
|
|
148
|
+
$stderr.puts "Warning: TrakFlow task creation failed: #{e.message}"
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
attr_reader :db
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
# Connect to the TrakFlow database if a project is initialized.
|
|
157
|
+
def connect_database
|
|
158
|
+
return nil unless TrakFlow.initialized?
|
|
159
|
+
|
|
160
|
+
db = TrakFlow::Storage::Database.new(TrakFlow.database_path)
|
|
161
|
+
db.connect
|
|
162
|
+
db
|
|
163
|
+
rescue StandardError
|
|
164
|
+
nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Find a task by its title (partial match).
|
|
168
|
+
def find_task_by_title(title)
|
|
169
|
+
tasks = @db.list_tasks(title: title)
|
|
170
|
+
tasks.first
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# lib/aia/turn_state.rb
|
|
4
|
+
#
|
|
5
|
+
# Request-scoped state object for inter-component signaling.
|
|
6
|
+
# Replaces ad-hoc instance variable flags on AIA.config.
|
|
7
|
+
#
|
|
8
|
+
# Only one special mode may be requested per turn (mutual exclusion).
|
|
9
|
+
# Use TurnState#request(mode, **opts) to enqueue a mode — this clears
|
|
10
|
+
# all other force_* flags before setting the new one. Assigning the
|
|
11
|
+
# attr_accessors directly bypasses mutual exclusion (for backward compat
|
|
12
|
+
# and direct test assertions).
|
|
13
|
+
|
|
14
|
+
module AIA
|
|
15
|
+
class TurnState
|
|
16
|
+
# Exclusive mode flags — only one may be active at a time via #request.
|
|
17
|
+
EXCLUSIVE_MODES = %i[
|
|
18
|
+
verify decompose concurrent_mcp debate delegate spawn orchestrate
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
attr_accessor :force_verify, :force_decompose, :force_concurrent_mcp,
|
|
22
|
+
:force_debate, :force_delegate, :force_spawn, :spawn_type,
|
|
23
|
+
:spawn_spec, :force_orchestrate, :active_mcp_servers
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
clear!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Request a special mode for this turn.
|
|
30
|
+
# Clears all other exclusive force_* flags before activating the new mode,
|
|
31
|
+
# enforcing mutual exclusion at the point of enqueue.
|
|
32
|
+
#
|
|
33
|
+
# @param mode [Symbol] one of EXCLUSIVE_MODES
|
|
34
|
+
# @param type [String, nil] specialist type (only relevant for :spawn)
|
|
35
|
+
# @raise [ArgumentError] if mode is not one of EXCLUSIVE_MODES
|
|
36
|
+
def request(mode, type: nil)
|
|
37
|
+
unless EXCLUSIVE_MODES.include?(mode)
|
|
38
|
+
raise ArgumentError, "Unknown TurnState mode: #{mode.inspect}. Valid: #{EXCLUSIVE_MODES.join(', ')}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
clear_exclusive_flags!
|
|
42
|
+
|
|
43
|
+
case mode
|
|
44
|
+
when :verify then @force_verify = true
|
|
45
|
+
when :decompose then @force_decompose = true
|
|
46
|
+
when :concurrent_mcp then @force_concurrent_mcp = true
|
|
47
|
+
when :debate then @force_debate = true
|
|
48
|
+
when :delegate then @force_delegate = true
|
|
49
|
+
when :spawn then @force_spawn = true
|
|
50
|
+
@spawn_type = type
|
|
51
|
+
when :orchestrate then @force_orchestrate = true
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def clear!
|
|
56
|
+
@force_verify = false
|
|
57
|
+
@force_decompose = false
|
|
58
|
+
@force_concurrent_mcp = false
|
|
59
|
+
@force_debate = false
|
|
60
|
+
@force_delegate = false
|
|
61
|
+
@force_spawn = false
|
|
62
|
+
@spawn_type = nil
|
|
63
|
+
@spawn_spec = nil
|
|
64
|
+
@force_orchestrate = false
|
|
65
|
+
@active_mcp_servers = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns which exclusive mode is currently active, or nil if none.
|
|
69
|
+
def active_mode
|
|
70
|
+
return :verify if @force_verify
|
|
71
|
+
return :decompose if @force_decompose
|
|
72
|
+
return :concurrent_mcp if @force_concurrent_mcp
|
|
73
|
+
return :debate if @force_debate
|
|
74
|
+
return :delegate if @force_delegate
|
|
75
|
+
return :spawn if @force_spawn
|
|
76
|
+
return :orchestrate if @force_orchestrate
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def clear_exclusive_flags!
|
|
83
|
+
@force_verify = false
|
|
84
|
+
@force_decompose = false
|
|
85
|
+
@force_concurrent_mcp = false
|
|
86
|
+
@force_debate = false
|
|
87
|
+
@force_delegate = false
|
|
88
|
+
@force_spawn = false
|
|
89
|
+
@spawn_type = nil
|
|
90
|
+
@spawn_spec = nil
|
|
91
|
+
@force_orchestrate = false
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|