ares-runtime 2.0.1
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 +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +110 -0
- data/config/models.yml +25 -0
- data/config/ollama.yml +4 -0
- data/config/workspaces.yml +6 -0
- data/exe/ares +75 -0
- data/lib/ares/cli.rb +37 -0
- data/lib/ares/runtime/adapters/base_adapter.rb +68 -0
- data/lib/ares/runtime/adapters/claude_adapter.rb +35 -0
- data/lib/ares/runtime/adapters/codex_adapter.rb +35 -0
- data/lib/ares/runtime/adapters/cursor_adapter.rb +32 -0
- data/lib/ares/runtime/adapters/ollama_adapter.rb +37 -0
- data/lib/ares/runtime/config_cli.rb +18 -0
- data/lib/ares/runtime/config_manager.rb +137 -0
- data/lib/ares/runtime/context_loader.rb +45 -0
- data/lib/ares/runtime/core_subsystem.rb +36 -0
- data/lib/ares/runtime/diagnostic_parser.rb +159 -0
- data/lib/ares/runtime/doctor.rb +34 -0
- data/lib/ares/runtime/engine_chain.rb +108 -0
- data/lib/ares/runtime/git_manager.rb +26 -0
- data/lib/ares/runtime/initializer.rb +30 -0
- data/lib/ares/runtime/logs_cli.rb +35 -0
- data/lib/ares/runtime/model_selector.rb +36 -0
- data/lib/ares/runtime/ollama_client_factory.rb +43 -0
- data/lib/ares/runtime/planner/ollama_planner.rb +51 -0
- data/lib/ares/runtime/planner/tiny_task_processor.rb +129 -0
- data/lib/ares/runtime/prompt_builder.rb +52 -0
- data/lib/ares/runtime/quota_manager.rb +48 -0
- data/lib/ares/runtime/router.rb +285 -0
- data/lib/ares/runtime/task_logger.rb +37 -0
- data/lib/ares/runtime/task_manager.rb +9 -0
- data/lib/ares/runtime/terminal_runner.rb +37 -0
- data/lib/ares/runtime/tui.rb +211 -0
- data/lib/ares/runtime/version.rb +7 -0
- data/lib/ares/runtime.rb +5 -0
- data/lib/ares_runtime.rb +63 -0
- metadata +240 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f1e365203d1a9e9c5744f269c549442361f39cf36da8525f311bae426947b58e
|
|
4
|
+
data.tar.gz: 96fc6b9900f2bea091cd6f90331eb09c51a7f751c5c42810fd9465e2d797d1b3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a0a770802c524ee3dbdadda2b8d0842e23d19c2c42943a763151a20cdc185fe6a11d0326b925fdaa907d9e165696df290a9c81cc3c3e16749f295c8f18deef21
|
|
7
|
+
data.tar.gz: be7b15ebe2c4cd3a4e80aadfdfa3ccddbe0c436876e9e9e115851acd9d2ce1c4b45db728c9c6d1f644e1a5b5ea45f1e9b2c8bf17c459456df036471c940414d0
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [2.0.0] - 2026-02-28
|
|
6
|
+
- Complete rewrite for Ares 2.0.
|
|
7
|
+
- Introduced Deterministic Multi-Agent Orchestration.
|
|
8
|
+
- Added Tiny Task Layer for diagnostic parsing.
|
|
9
|
+
- Integrated TTY toolkit for advanced CLI UX.
|
|
10
|
+
- Gemified project for distribution.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shubhamtaywade82@gmail.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# 🧠 Ares 2.0: Agent Orchestrator
|
|
2
|
+
|
|
3
|
+
A production-grade, deterministic multi-agent orchestrator CLI. Ares routes tasks to the best localized executor (Claude, Codex, or Cursor) based on a strategic planning phase powered by local Ollama.
|
|
4
|
+
|
|
5
|
+
## 🏗️ Architecture
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
graph TD
|
|
9
|
+
User["User Task"] --> Router["Router (Ares CLI)"]
|
|
10
|
+
Router --> Planner["Ollama Planner (Local qwen3:latest)"]
|
|
11
|
+
Planner --> Router
|
|
12
|
+
Router --> TinyTask["Tiny Task Layer (Diagnostics)"]
|
|
13
|
+
TinyTask --> Router
|
|
14
|
+
Router --> Selector["Model Selector (Config-Driven)"]
|
|
15
|
+
Selector --> Router
|
|
16
|
+
Router --> Quota["Quota Manager"]
|
|
17
|
+
Router --> Logger["Task Logger (UUID)"]
|
|
18
|
+
Router --> Git["Git Manager (Auto-branch)"]
|
|
19
|
+
Router --> Adapter["Engine Adapter (Claude/Codex/Cursor)"]
|
|
20
|
+
Adapter --> Router
|
|
21
|
+
Router --> Verify["Post-Fix Verification"]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **Planning Layer**: Uses local Ollama model to classify tasks, assign risk/confidence scores, and decompose tasks into discrete slices.
|
|
25
|
+
- **Tiny Task Layer (Diagnostics)**: Offloads raw terminal output parsing and diff summarization to local Ollama, reducing token load on Claude by 60-80%.
|
|
26
|
+
- **Routing Layer**: Deterministic rules in `config/ares/models.yml` allocate tasks to engines based on type and risk.
|
|
27
|
+
- **Automated Fix Loop**: Detects failures, summarizes them locally, escalates for a fix, and re-verifies automatically.
|
|
28
|
+
- **Traceability**: Every task receives a UUID and is logged in `logs/UUID.json`.
|
|
29
|
+
- **Safety**: Built-in quota tracking and confidence-based escalation to Claude Opus for high-risk work.
|
|
30
|
+
|
|
31
|
+
## 📟 Terminal User Interface (TUI)
|
|
32
|
+
|
|
33
|
+
Ares 2.0 includes a professional, interactive dashboard for real-time task management.
|
|
34
|
+
```bash
|
|
35
|
+
bin/ares --tui
|
|
36
|
+
```
|
|
37
|
+
- **Live Monitoring**: Track quotas, task history, and AI reasoning live.
|
|
38
|
+
- **In-App Config**: Change models and Ollama settings without leaving the CLI.
|
|
39
|
+
- **Universal Fixes**: Trigger test, lint, or syntax healing with one keypress.
|
|
40
|
+
|
|
41
|
+
## 📚 Documentation & Guides
|
|
42
|
+
|
|
43
|
+
- [**TUI Guide**](file:///home/nemesis/project/agent-orchestrator/docs/TUI_GUIDE.md): Master the interactive dashboard.
|
|
44
|
+
- [**Self-Healing Guide**](file:///home/nemesis/project/agent-orchestrator/docs/SELF_HEALING.md): How Ares automatically repairs your code.
|
|
45
|
+
- [**Configuration Guide**](file:///home/nemesis/project/agent-orchestrator/docs/CONFIGURATION.md): Tuning model routing and local AI parameters.
|
|
46
|
+
|
|
47
|
+
## 🚀 Usage
|
|
48
|
+
|
|
49
|
+
### Installation (from RubyGems)
|
|
50
|
+
```bash
|
|
51
|
+
gem install ares-runtime
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Development Setup
|
|
55
|
+
Install dependencies:
|
|
56
|
+
```bash
|
|
57
|
+
bundle install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Run a task:
|
|
61
|
+
```bash
|
|
62
|
+
# Using the installed gem
|
|
63
|
+
ares "Task description"
|
|
64
|
+
|
|
65
|
+
# Or using the local executable
|
|
66
|
+
exe/ares "Task description"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Automated Diagnostic Loop
|
|
70
|
+
To run tests and automatically attempt fixes for failures:
|
|
71
|
+
```bash
|
|
72
|
+
exe/ares "run tests"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Flags
|
|
76
|
+
- `-d, --dry-run`: Plan and select model without execution.
|
|
77
|
+
- `-g, --git`: Auto-branch before execution and auto-commit results.
|
|
78
|
+
|
|
79
|
+
## 🎯 Model Routing Rules
|
|
80
|
+
|
|
81
|
+
Routed via `config/models.yml`:
|
|
82
|
+
- **Architecture**: Claude Opus (High Reasoning)
|
|
83
|
+
- **Refactor**: Claude Sonnet (Primary Executor)
|
|
84
|
+
- **Bulk Patch / Test Gen**: Codex (High Speed)
|
|
85
|
+
- **Interactive Edit**: Cursor Agent (Human-in-the-loop)
|
|
86
|
+
- **Summarization**: Claude Haiku (Low Cost)
|
|
87
|
+
|
|
88
|
+
## 🔐 Safety & Project Hygiene
|
|
89
|
+
|
|
90
|
+
1. **Deterministic One-Hop**: No recursive agent loops.
|
|
91
|
+
2. **Quota Aware**: Claude usage is tracked daily via `QuotaManager`.
|
|
92
|
+
3. **Workspace Isolation**: Execution is pinned to the current directory's context via recursive `AGENTS.md` discovery.
|
|
93
|
+
4. **Git Protection**: `.gitignore` automatically excludes `logs/`, `*.gem`, and AI-specific session/cache directories (`.claude`, `.cursor`, etc.).
|
|
94
|
+
|
|
95
|
+
## 🛠️ Configuration
|
|
96
|
+
|
|
97
|
+
- `config/models.yml`: Define routing logic and confidence thresholds.
|
|
98
|
+
- `config/workspaces.yml`: Register explicit workspace roots.
|
|
99
|
+
- `config/planner_schema.rb`: The strict JSON schema for the Ollama planner.
|
|
100
|
+
|
|
101
|
+
## 🛤️ Roadmap
|
|
102
|
+
|
|
103
|
+
- [ ] **Cost Tracker**: USD/token cost calculation per engine.
|
|
104
|
+
- [ ] **Parallel Execution**: Execute independent task slices concurrently.
|
|
105
|
+
- [ ] **Automatic Diff Chunking**: Handle massive diffs by chunking before LLM processing.
|
|
106
|
+
- [ ] **Rate Limiting**: Intelligent throttling to prevent mid-workflow blocks.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
**Author**: Antigravity (shubhamtaywade82@gmail.com)
|
|
110
|
+
**Repository**: [github.com/shubhamtaywade82/agent-orchestrator](https://github.com/shubhamtaywade82/agent-orchestrator)
|
data/config/models.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
architecture:
|
|
2
|
+
engine: claude
|
|
3
|
+
model: opus
|
|
4
|
+
|
|
5
|
+
refactor:
|
|
6
|
+
engine: claude
|
|
7
|
+
model: sonnet
|
|
8
|
+
|
|
9
|
+
bulk_patch:
|
|
10
|
+
engine: codex
|
|
11
|
+
model: default
|
|
12
|
+
|
|
13
|
+
test_generation:
|
|
14
|
+
engine: codex
|
|
15
|
+
model: default
|
|
16
|
+
|
|
17
|
+
summarization:
|
|
18
|
+
engine: ollama
|
|
19
|
+
model: default
|
|
20
|
+
|
|
21
|
+
interactive_edit:
|
|
22
|
+
engine: cursor
|
|
23
|
+
|
|
24
|
+
pr_automation:
|
|
25
|
+
engine: codex
|
data/config/ollama.yml
ADDED
data/exe/ares
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'ares_runtime'
|
|
5
|
+
require 'optparse'
|
|
6
|
+
|
|
7
|
+
module Ares
|
|
8
|
+
class CLI
|
|
9
|
+
def self.start
|
|
10
|
+
command = ARGV.shift
|
|
11
|
+
|
|
12
|
+
case command
|
|
13
|
+
when 'init' then init
|
|
14
|
+
when 'config' then config
|
|
15
|
+
when 'doctor' then doctor
|
|
16
|
+
when 'version' then version
|
|
17
|
+
when 'logs' then logs
|
|
18
|
+
else
|
|
19
|
+
run_task(command)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.init
|
|
24
|
+
Ares::Runtime::Initializer.run
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.config
|
|
28
|
+
Ares::Runtime::ConfigCLI.run
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.doctor
|
|
32
|
+
Ares::Runtime::Doctor.run
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.version
|
|
36
|
+
puts "Ares v#{Ares::Runtime::VERSION}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.logs
|
|
40
|
+
Ares::Runtime::LogsCLI.run
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.run_task(first_arg)
|
|
44
|
+
options = {
|
|
45
|
+
dry_run: false,
|
|
46
|
+
git: false,
|
|
47
|
+
tui: false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
OptionParser.new do |opts|
|
|
51
|
+
opts.banner = 'Usage: ares [options] "task description"'
|
|
52
|
+
|
|
53
|
+
opts.on('-d', '--dry-run') { options[:dry_run] = true }
|
|
54
|
+
opts.on('-g', '--git') { options[:git] = true }
|
|
55
|
+
opts.on('--tui') { options[:tui] = true }
|
|
56
|
+
end.parse!
|
|
57
|
+
|
|
58
|
+
if options[:tui]
|
|
59
|
+
Ares::Runtime::Tui.start
|
|
60
|
+
exit
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
task = ([first_arg] + ARGV).compact.join(' ').strip
|
|
64
|
+
|
|
65
|
+
if task.empty?
|
|
66
|
+
puts 'No task provided.'
|
|
67
|
+
exit 1
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
Ares::Runtime::Router.new.run(task, options)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
Ares::CLI.start
|
data/lib/ares/cli.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'runtime/config_manager'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Ares
|
|
7
|
+
class CLI
|
|
8
|
+
def self.init
|
|
9
|
+
root = Ares::Runtime::ConfigManager.project_root
|
|
10
|
+
target_dir = File.join(root, 'config', 'ares')
|
|
11
|
+
|
|
12
|
+
if Dir.exist?(target_dir)
|
|
13
|
+
puts "Ares config already exists at #{target_dir}"
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
FileUtils.mkdir_p(target_dir)
|
|
18
|
+
|
|
19
|
+
copy_default('models.yml', target_dir)
|
|
20
|
+
copy_default('ollama.yml', target_dir)
|
|
21
|
+
|
|
22
|
+
puts "Ares initialized at #{target_dir}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.copy_default(filename, target_dir)
|
|
26
|
+
gem_root = Gem::Specification.find_by_name('agent-orchestrator').gem_dir
|
|
27
|
+
source = File.join(gem_root, 'config', filename)
|
|
28
|
+
|
|
29
|
+
unless File.exist?(source)
|
|
30
|
+
puts "Warning: default #{filename} not found in gem."
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
FileUtils.cp(source, File.join(target_dir, filename))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'timeout'
|
|
5
|
+
|
|
6
|
+
module Ares
|
|
7
|
+
module Runtime
|
|
8
|
+
# Abstract base class implementing the Template Method pattern for CLI adapters.
|
|
9
|
+
# Defines the skeleton for execution, timeouts, and retries.
|
|
10
|
+
class BaseAdapter
|
|
11
|
+
DEFAULT_TIMEOUT = 30
|
|
12
|
+
|
|
13
|
+
# Template Method: The core algorithm skeleton
|
|
14
|
+
def call(prompt, model = nil, **options)
|
|
15
|
+
cmd = build_command(prompt, model, **options)
|
|
16
|
+
|
|
17
|
+
output, status = execute_with_timeout(cmd, prompt, timeout_seconds)
|
|
18
|
+
|
|
19
|
+
if should_retry?(status, output)
|
|
20
|
+
cmd = build_retry_command(cmd, prompt, **options)
|
|
21
|
+
output, status = execute_with_timeout(cmd, prompt, timeout_seconds)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
handle_errors(status, output)
|
|
25
|
+
|
|
26
|
+
output
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
protected
|
|
30
|
+
|
|
31
|
+
def execute_with_timeout(cmd, prompt, timeout)
|
|
32
|
+
Timeout.timeout(timeout) do
|
|
33
|
+
Open3.capture2e(*cmd, stdin_data: prompt)
|
|
34
|
+
end
|
|
35
|
+
rescue Timeout::Error => e
|
|
36
|
+
raise "#{adapter_name} timed out after #{timeout}s: #{e.message}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def handle_errors(status, output)
|
|
40
|
+
raise "#{adapter_name} command failed: #{output}" unless status.success?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Subclasses MUST implement this
|
|
44
|
+
def build_command(prompt, model, **options)
|
|
45
|
+
raise NotImplementedError, "#{self.class} must implement #build_command"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Hook: Override in subclasses for complex retry logic
|
|
49
|
+
def should_retry?(_status, _output)
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Hook: Customize the command for the retry attempt
|
|
54
|
+
def build_retry_command(cmd, _prompt, **_options)
|
|
55
|
+
cmd
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Hook: Override for specific adapter timeouts
|
|
59
|
+
def timeout_seconds
|
|
60
|
+
DEFAULT_TIMEOUT
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def adapter_name
|
|
64
|
+
self.class.name.split('::').last.sub('Adapter', '')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_adapter'
|
|
4
|
+
|
|
5
|
+
module Ares
|
|
6
|
+
module Runtime
|
|
7
|
+
# Adapter for Claude CLI. Passes prompts via stdin to avoid ARG_MAX limits.
|
|
8
|
+
class ClaudeAdapter < BaseAdapter
|
|
9
|
+
def call(prompt, model = nil, fork_session: false, **_options)
|
|
10
|
+
check_auth!
|
|
11
|
+
super(prompt, model, fork_session: fork_session)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
def build_command(_prompt, model, fork_session: false, **_options)
|
|
17
|
+
model ||= 'sonnet'
|
|
18
|
+
# -p - means read prompt from stdin
|
|
19
|
+
# --allow-dangerously-skip-permissions bypasses interactive prompts
|
|
20
|
+
cmd = ['claude', '--model', model, '-p', '-', '--allow-dangerously-skip-permissions']
|
|
21
|
+
cmd += %w[--continue --fork-session] if fork_session
|
|
22
|
+
cmd
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def check_auth!
|
|
28
|
+
system('claude auth status > /dev/null 2>&1')
|
|
29
|
+
return if $CHILD_STATUS.success?
|
|
30
|
+
|
|
31
|
+
raise 'Claude CLI not logged in. Please run `claude login` in your terminal.'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_adapter'
|
|
4
|
+
|
|
5
|
+
module Ares
|
|
6
|
+
module Runtime
|
|
7
|
+
# Adapter for OpenAI Codex CLI. Uses exec mode with full automation for headless environments.
|
|
8
|
+
class CodexAdapter < BaseAdapter
|
|
9
|
+
def call(prompt, model = nil, resume: true, **_options)
|
|
10
|
+
super(prompt, model, resume: resume)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def apply_cloud_task(task_id)
|
|
14
|
+
cmd = ['codex', 'apply', task_id]
|
|
15
|
+
Ares::Runtime::TerminalRunner.run(cmd)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
def build_command(_prompt, _model, resume: true, **_options)
|
|
21
|
+
cmd = ['codex', 'exec', '--full-auto', '-']
|
|
22
|
+
cmd << '--resume' if resume
|
|
23
|
+
cmd
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def should_retry?(status, output)
|
|
27
|
+
!status.success? && (output.include?('No session found') || output.include?('error: unexpected argument'))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def build_retry_command(cmd, _prompt, **_options)
|
|
31
|
+
cmd.dup.tap { |c| c.delete('--resume') }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_adapter'
|
|
4
|
+
|
|
5
|
+
module Ares
|
|
6
|
+
module Runtime
|
|
7
|
+
# Adapter for Cursor CLI (agent). Uses stdin piping and trust flags for automation.
|
|
8
|
+
class CursorAdapter < BaseAdapter
|
|
9
|
+
def call(prompt, model = nil, resume: true, cloud: false, **_options)
|
|
10
|
+
super(prompt, model, resume: resume, cloud: cloud)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
def build_command(_prompt, _model, resume: true, cloud: false, **_options)
|
|
16
|
+
# --trust --yolo ensures no interactive prompts in headless mode
|
|
17
|
+
cmd = ['agent', '-p', '-', '--trust', '--yolo']
|
|
18
|
+
cmd << '-c' if cloud
|
|
19
|
+
cmd << '--continue' if resume && !cloud
|
|
20
|
+
cmd
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def should_retry?(status, output)
|
|
24
|
+
!status.success? && output.include?('No previous chats found')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def build_retry_command(cmd, _prompt, **_options)
|
|
28
|
+
cmd.dup.tap { |c| c.delete('--continue') }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ares
|
|
4
|
+
module Runtime
|
|
5
|
+
class OllamaAdapter
|
|
6
|
+
def initialize
|
|
7
|
+
config_data = ConfigManager.load_ollama
|
|
8
|
+
config = Ollama::Config.new
|
|
9
|
+
config.base_url = config_data[:base_url]
|
|
10
|
+
config.timeout = config_data[:timeout]
|
|
11
|
+
config.num_ctx = config_data[:num_ctx]
|
|
12
|
+
config.retries = config_data[:retries]
|
|
13
|
+
|
|
14
|
+
@client = Ollama::Client.new(config: config)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(prompt, model = nil, schema: nil)
|
|
18
|
+
model ||= best_available_model
|
|
19
|
+
|
|
20
|
+
options = { prompt: prompt, model: model }
|
|
21
|
+
options[:schema] = schema if schema
|
|
22
|
+
|
|
23
|
+
@client.generate(**options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def best_available_model
|
|
29
|
+
available = @client.list_model_names
|
|
30
|
+
return 'qwen3:latest' if available.include?('qwen3:latest')
|
|
31
|
+
return 'qwen3:8b' if available.include?('qwen3:8b')
|
|
32
|
+
|
|
33
|
+
available.first || 'qwen3:8b' # Fallback
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ares
|
|
4
|
+
module Runtime
|
|
5
|
+
class ConfigCLI
|
|
6
|
+
def self.run
|
|
7
|
+
models = ConfigManager.load_models
|
|
8
|
+
ollama = ConfigManager.load_ollama
|
|
9
|
+
|
|
10
|
+
puts "\nModels Configuration:"
|
|
11
|
+
puts models.to_yaml
|
|
12
|
+
|
|
13
|
+
puts "\nOllama Configuration:"
|
|
14
|
+
puts ollama.to_yaml
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Ares
|
|
7
|
+
module Runtime
|
|
8
|
+
class ConfigManager
|
|
9
|
+
GLOBAL_DIR = File.expand_path('~/.ares')
|
|
10
|
+
|
|
11
|
+
# Public API -----------------------------------------------------------
|
|
12
|
+
def self.load_models
|
|
13
|
+
load_merged('models.yml')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.load_ollama
|
|
17
|
+
load_merged('ollama.yml')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.save_models(config)
|
|
21
|
+
save_config('models.yml', config)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.save_ollama(config)
|
|
25
|
+
save_config('ollama.yml', config)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.task_types
|
|
29
|
+
load_models.keys
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.update_task_config(task_type, engine, model = nil)
|
|
33
|
+
config = load_models
|
|
34
|
+
config[task_type] = { engine: engine, model: model }
|
|
35
|
+
save_models(config)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# -------------------------------------------------------------------
|
|
39
|
+
# Internal helpers
|
|
40
|
+
def self.load_merged(filename)
|
|
41
|
+
merged = {}
|
|
42
|
+
merged = deep_merge(merged, load_file(gem_default_path(filename)))
|
|
43
|
+
merged = deep_merge(merged, load_file(global_path(filename)))
|
|
44
|
+
deep_merge(merged, load_file(local_path(filename)))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.load_file(path)
|
|
48
|
+
return {} unless File.exist?(path)
|
|
49
|
+
|
|
50
|
+
data = YAML.load_file(path)
|
|
51
|
+
return {} unless data.is_a?(Hash)
|
|
52
|
+
|
|
53
|
+
symbolize_keys(data)
|
|
54
|
+
rescue StandardError
|
|
55
|
+
{}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.save_config(filename, config)
|
|
59
|
+
target = File.exist?(local_path(filename)) ? local_path(filename) : global_path(filename)
|
|
60
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
61
|
+
File.write(target, stringify_keys(config || {}).to_yaml)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Path helpers --------------------------------------------------------
|
|
65
|
+
def self.project_root
|
|
66
|
+
dir = Dir.pwd
|
|
67
|
+
|
|
68
|
+
loop do
|
|
69
|
+
# Look for the namespaced config directory
|
|
70
|
+
return dir if File.exist?(File.join(dir, 'config', 'ares', 'models.yml'))
|
|
71
|
+
|
|
72
|
+
parent = File.dirname(dir)
|
|
73
|
+
break if parent == dir
|
|
74
|
+
|
|
75
|
+
dir = parent
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
Dir.pwd
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.local_path(filename)
|
|
82
|
+
File.join(project_root, 'config', 'ares', filename)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.global_path(filename)
|
|
86
|
+
File.join(GLOBAL_DIR, filename)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.gem_default_path(filename)
|
|
90
|
+
spec = Gem.loaded_specs['ares-runtime'] || begin
|
|
91
|
+
Gem::Specification.find_by_name('ares-runtime')
|
|
92
|
+
rescue StandardError
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
if spec
|
|
96
|
+
File.join(spec.gem_dir, 'config', filename)
|
|
97
|
+
else
|
|
98
|
+
File.expand_path("../../config/#{filename}", __dir__)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Utility methods ------------------------------------------------------
|
|
103
|
+
def self.deep_merge(hash1, hash2)
|
|
104
|
+
result = hash1.dup
|
|
105
|
+
hash2.each do |key, value|
|
|
106
|
+
result[key] = if result[key].is_a?(Hash) && value.is_a?(Hash)
|
|
107
|
+
deep_merge(result[key], value)
|
|
108
|
+
else
|
|
109
|
+
value
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
result
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.symbolize_keys(hash)
|
|
116
|
+
return {} unless hash.is_a?(Hash)
|
|
117
|
+
|
|
118
|
+
hash.each_with_object({}) do |(k, v), h|
|
|
119
|
+
key = begin
|
|
120
|
+
k.to_sym
|
|
121
|
+
rescue StandardError
|
|
122
|
+
k
|
|
123
|
+
end
|
|
124
|
+
h[key] = v.is_a?(Hash) ? symbolize_keys(v) : v
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.stringify_keys(hash)
|
|
129
|
+
return {} unless hash.is_a?(Hash)
|
|
130
|
+
|
|
131
|
+
hash.transform_keys(&:to_s).each_with_object({}) do |(k, v), h|
|
|
132
|
+
h[k] = v.is_a?(Hash) ? stringify_keys(v) : v
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|