fosm-rails-coding-agent 0.0.2 → 0.0.4
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6aeb8d53bfaadc57317556a9e91f4c95fb633a8e3f365ca6ec3517e09b82af7
|
|
4
|
+
data.tar.gz: 062c37b29a701991b14c0f56782612f8c56b37bc2b16a171e70d434eb7078d43
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6cb0787f6e6937434f169847d67d7b0e650cab59e50b28f81b61a54047ec2975f7503a8630a6f4dcd3b25f83ed2cad825459093181a42d4bdacbfbe742beb364
|
|
7
|
+
data.tar.gz: f97879752c8e85594dea933577b0e20abccb4862a956af02ad87032913480b750b6e71d8dca1e3031503033defc82b7ac0b760afba73ab7081b749cc1cc23e6a
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to fosm-rails-coding-agent will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.0.4] - 2026-03-29
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **fosm-coding-agent is now a real coding agent** powered by Gemlings ToolCallingAgent.
|
|
10
|
+
Previously returned canned responses. Now uses an LLM (Claude Sonnet by default)
|
|
11
|
+
to read/write files, run bash commands, and reason step by step.
|
|
12
|
+
- ACP prompts are handled by Gemlings agent.run() with streaming step notifications.
|
|
13
|
+
- Added BashTool for running shell commands.
|
|
14
|
+
- Added gemlings >= 0.3 as a direct dependency.
|
|
15
|
+
- Agent model configurable via FOSM_AGENT_MODEL env var (default: anthropic/claude-sonnet-4-20250514).
|
|
16
|
+
|
|
5
17
|
## [0.0.1] - 2026-03-29
|
|
6
18
|
|
|
7
19
|
### Added
|
|
@@ -3,77 +3,198 @@
|
|
|
3
3
|
require "agent_client_protocol"
|
|
4
4
|
require "json"
|
|
5
5
|
require "securerandom"
|
|
6
|
+
require "open3"
|
|
7
|
+
require "gemlings"
|
|
8
|
+
require "gemlings/tools/file_read"
|
|
9
|
+
require "gemlings/tools/file_write"
|
|
6
10
|
|
|
7
11
|
module FosmRailsCodingAgent
|
|
8
|
-
# ACP agent
|
|
9
|
-
# Provides push-based FOSM lifecycle context to coding agents.
|
|
12
|
+
# ACP agent powered by Gemlings ToolCallingAgent.
|
|
10
13
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# - Recent transition activity
|
|
14
|
+
# Bridges the Agent Client Protocol (stdio JSON-RPC) to a real LLM-powered
|
|
15
|
+
# coding agent. Each prompt is handled by a Gemlings agent that can read/write
|
|
16
|
+
# files, call tools, and reason step by step — not canned responses.
|
|
15
17
|
#
|
|
16
|
-
#
|
|
17
|
-
# PUSH context so the coding agent starts with full situational awareness.
|
|
18
|
+
# On session start, FOSM lifecycle context is pushed proactively.
|
|
18
19
|
class AcpAgent
|
|
19
20
|
include AgentClientProtocol::AgentInterface
|
|
20
21
|
include AgentClientProtocol::Helpers
|
|
21
22
|
|
|
23
|
+
DEFAULT_MODEL = "anthropic/claude-sonnet-4-20250514"
|
|
24
|
+
|
|
22
25
|
def on_connect(conn)
|
|
23
26
|
@conn = conn
|
|
27
|
+
@sessions = {} # session_id => Gemlings::ToolCallingAgent
|
|
24
28
|
end
|
|
25
29
|
|
|
26
|
-
def initialize_agent(protocol_version:, **)
|
|
30
|
+
def initialize_agent(protocol_version:, **_)
|
|
27
31
|
AgentClientProtocol::Schema::InitializeResponse.new(
|
|
28
32
|
protocol_version: AgentClientProtocol::PROTOCOL_VERSION,
|
|
29
33
|
agent_info: AgentClientProtocol::Schema::Implementation.new(
|
|
30
|
-
name: "fosm-
|
|
34
|
+
name: "fosm-coding-agent",
|
|
31
35
|
version: FosmRailsCodingAgent::VERSION
|
|
32
36
|
),
|
|
33
37
|
capabilities: AgentClientProtocol::Schema::AgentCapabilities.new
|
|
34
38
|
)
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
def new_session(cwd:, **)
|
|
38
|
-
session_id = "fosm
|
|
41
|
+
def new_session(cwd:, **_)
|
|
42
|
+
session_id = "fosm-#{SecureRandom.hex(8)}"
|
|
43
|
+
|
|
44
|
+
# Build a Gemlings agent with coding tools + FOSM context
|
|
45
|
+
agent = build_agent(cwd)
|
|
46
|
+
@sessions[session_id] = agent
|
|
39
47
|
|
|
40
|
-
#
|
|
41
|
-
push_fosm_context(session_id)
|
|
48
|
+
# Push FOSM context proactively
|
|
49
|
+
push_fosm_context(session_id)
|
|
42
50
|
|
|
43
51
|
AgentClientProtocol::Schema::NewSessionResponse.new(
|
|
44
52
|
session_id: session_id
|
|
45
53
|
)
|
|
46
54
|
end
|
|
47
55
|
|
|
48
|
-
def prompt(prompt:, session_id:, **)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
def prompt(prompt:, session_id:, **_)
|
|
57
|
+
agent = @sessions[session_id]
|
|
58
|
+
unless agent
|
|
59
|
+
send_error_message(session_id, "Session not found. Create a new session first.")
|
|
60
|
+
return end_turn
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Extract text from ACP prompt content blocks
|
|
64
|
+
text = prompt.map { |block|
|
|
65
|
+
case block
|
|
66
|
+
when AgentClientProtocol::Schema::TextContent then block.text
|
|
67
|
+
when Hash then block["text"] || block.inspect
|
|
68
|
+
else block.inspect
|
|
69
|
+
end
|
|
70
|
+
}.join("\n")
|
|
71
|
+
|
|
72
|
+
# Run the Gemlings agent in streaming mode — forward steps as ACP notifications
|
|
73
|
+
run_agent(agent, text, session_id)
|
|
74
|
+
|
|
75
|
+
end_turn
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def cancel(session_id:, **_)
|
|
79
|
+
agent = @sessions[session_id]
|
|
80
|
+
agent&.interrupt
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def authenticate(method_id:, **_)
|
|
84
|
+
AgentClientProtocol::Schema::AuthenticateResponse.new
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def end_turn
|
|
90
|
+
AgentClientProtocol::Schema::PromptResponse.new(
|
|
91
|
+
stop_reason: AgentClientProtocol::Schema::StopReason::END_TURN
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Build a Gemlings ToolCallingAgent with file tools and FOSM instructions.
|
|
96
|
+
def build_agent(cwd)
|
|
97
|
+
tools = [Gemlings::FileRead, Gemlings::FileWrite, BashTool]
|
|
98
|
+
|
|
99
|
+
model = ENV.fetch("FOSM_AGENT_MODEL", DEFAULT_MODEL)
|
|
100
|
+
instructions = build_instructions(cwd)
|
|
101
|
+
|
|
102
|
+
Gemlings::ToolCallingAgent.new(
|
|
103
|
+
model: model,
|
|
104
|
+
tools: tools,
|
|
105
|
+
max_steps: 20,
|
|
106
|
+
instructions: instructions,
|
|
107
|
+
name: "fosm-coding-agent"
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_instructions(cwd)
|
|
112
|
+
instructions = <<~INST
|
|
113
|
+
You are a coding agent working in a Rails application at: #{cwd}
|
|
114
|
+
|
|
115
|
+
You can read files, write files, and run bash commands to help the developer.
|
|
116
|
+
When asked about the app, explore the codebase. When asked to make changes, do so.
|
|
117
|
+
Always explain what you're doing before making changes.
|
|
118
|
+
INST
|
|
119
|
+
|
|
120
|
+
# Append FOSM context if available
|
|
121
|
+
fosm_context = build_fosm_context
|
|
122
|
+
instructions += "\n\n#{fosm_context}" unless fosm_context.empty?
|
|
123
|
+
|
|
124
|
+
instructions
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Run the agent and stream results back as ACP notifications.
|
|
128
|
+
def run_agent(agent, task, session_id)
|
|
129
|
+
# Use step-by-step execution so we can stream each step
|
|
130
|
+
agent.reset!
|
|
131
|
+
step = agent.step(task)
|
|
132
|
+
stream_step(step, session_id)
|
|
133
|
+
|
|
134
|
+
until agent.done?
|
|
135
|
+
step = agent.step
|
|
136
|
+
stream_step(step, session_id)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Send the final answer
|
|
140
|
+
if agent.final_answer_value
|
|
141
|
+
send_agent_message(session_id, agent.final_answer_value.to_s)
|
|
142
|
+
end
|
|
143
|
+
rescue Gemlings::MaxStepsError
|
|
144
|
+
send_agent_message(session_id, "Reached maximum steps. Please provide more guidance.")
|
|
145
|
+
rescue Gemlings::InterruptError
|
|
146
|
+
send_agent_message(session_id, "Cancelled.")
|
|
147
|
+
rescue => e
|
|
148
|
+
send_error_message(session_id, "Agent error: #{e.message}")
|
|
149
|
+
end
|
|
55
150
|
|
|
56
|
-
|
|
151
|
+
# Convert a Gemlings step into ACP session/update notifications.
|
|
152
|
+
def stream_step(step, session_id)
|
|
153
|
+
return unless step
|
|
57
154
|
|
|
155
|
+
# Stream thought
|
|
156
|
+
if step.respond_to?(:thought) && step.thought
|
|
58
157
|
@conn.session_update(
|
|
59
158
|
session_id: session_id,
|
|
60
|
-
update:
|
|
159
|
+
update: update_agent_thought_text(step.thought)
|
|
61
160
|
)
|
|
62
161
|
end
|
|
63
162
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
163
|
+
# Stream tool calls
|
|
164
|
+
if step.respond_to?(:tool_calls) && step.tool_calls&.any?
|
|
165
|
+
step.tool_calls.each do |tc|
|
|
166
|
+
@conn.session_update(
|
|
167
|
+
session_id: session_id,
|
|
168
|
+
update: start_tool_call(
|
|
169
|
+
tc.id || SecureRandom.hex(8),
|
|
170
|
+
"#{tc.function.name}(#{tc.function.arguments.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")})",
|
|
171
|
+
status: AgentClientProtocol::Schema::ToolCallStatus::COMPLETED
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Stream observation/output
|
|
178
|
+
if step.respond_to?(:observation) && step.observation
|
|
179
|
+
send_agent_message(session_id, step.observation)
|
|
180
|
+
end
|
|
67
181
|
end
|
|
68
182
|
|
|
69
|
-
def
|
|
70
|
-
|
|
183
|
+
def send_agent_message(session_id, text)
|
|
184
|
+
@conn.session_update(
|
|
185
|
+
session_id: session_id,
|
|
186
|
+
update: update_agent_message_text(text)
|
|
187
|
+
)
|
|
71
188
|
end
|
|
72
189
|
|
|
73
|
-
|
|
190
|
+
def send_error_message(session_id, text)
|
|
191
|
+
@conn.session_update(
|
|
192
|
+
session_id: session_id,
|
|
193
|
+
update: update_agent_message_text("Error: #{text}")
|
|
194
|
+
)
|
|
195
|
+
end
|
|
74
196
|
|
|
75
|
-
# Push FOSM lifecycle context at session start
|
|
76
|
-
# has full awareness of the state machines in the project.
|
|
197
|
+
# Push FOSM lifecycle context at session start.
|
|
77
198
|
def push_fosm_context(session_id)
|
|
78
199
|
context = build_fosm_context
|
|
79
200
|
return if context.empty?
|
|
@@ -82,8 +203,7 @@ module FosmRailsCodingAgent
|
|
|
82
203
|
session_id: session_id,
|
|
83
204
|
update: update_agent_message_text(
|
|
84
205
|
"## FOSM Lifecycle Context\n\n" \
|
|
85
|
-
"This Rails application uses FOSM
|
|
86
|
-
"lifecycles. Here are the registered state machines:\n\n#{context}"
|
|
206
|
+
"This Rails application uses FOSM lifecycles:\n\n#{context}"
|
|
87
207
|
)
|
|
88
208
|
)
|
|
89
209
|
end
|
|
@@ -109,24 +229,27 @@ module FosmRailsCodingAgent
|
|
|
109
229
|
" #{e.name}: #{e.from_states.join(", ")} → #{e.to_state}"
|
|
110
230
|
end
|
|
111
231
|
|
|
112
|
-
"### #{klass.name}\
|
|
113
|
-
"States: #{states.join(", ")}\n" \
|
|
114
|
-
"Events:\n#{events.join("\n")}\n"
|
|
232
|
+
"### #{klass.name}\nStates: #{states.join(", ")}\nEvents:\n#{events.join("\n")}\n"
|
|
115
233
|
end.join("\n")
|
|
116
234
|
end
|
|
235
|
+
end
|
|
117
236
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
237
|
+
# Simple bash tool for the Gemlings agent.
|
|
238
|
+
class BashTool < Gemlings::Tool
|
|
239
|
+
tool_name "bash"
|
|
240
|
+
description "Execute a bash command and return stdout/stderr. Use for running tests, git, rake, etc."
|
|
241
|
+
input :command, type: :string, description: "The bash command to execute"
|
|
242
|
+
output_type :string
|
|
243
|
+
|
|
244
|
+
def call(command:)
|
|
245
|
+
stdout, stderr, status = Open3.capture3(command)
|
|
246
|
+
output = ""
|
|
247
|
+
output += stdout unless stdout.empty?
|
|
248
|
+
output += "\nSTDERR: #{stderr}" unless stderr.empty?
|
|
249
|
+
output += "\nExit: #{status.exitstatus}" unless status.success?
|
|
250
|
+
output.empty? ? "(no output)" : output.strip
|
|
251
|
+
rescue => e
|
|
252
|
+
"Error: #{e.message}"
|
|
130
253
|
end
|
|
131
254
|
end
|
|
132
255
|
end
|
|
@@ -7,7 +7,7 @@ module Fosm
|
|
|
7
7
|
# Usage:
|
|
8
8
|
# rails generate fosm:mcp_config
|
|
9
9
|
# rails generate fosm:mcp_config --port=4000
|
|
10
|
-
class McpConfigGenerator < Rails::Generators::Base
|
|
10
|
+
class McpConfigGenerator < ::Rails::Generators::Base
|
|
11
11
|
desc "Creates .mcp.json for Claude Code / Codex / Copilot MCP integration"
|
|
12
12
|
|
|
13
13
|
class_option :port, type: :numeric, default: 3000,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fosm-rails-coding-agent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Abhishek Parolkar
|
|
@@ -66,6 +66,20 @@ dependencies:
|
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0.1'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: gemlings
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0.3'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0.3'
|
|
69
83
|
description: |
|
|
70
84
|
Embeds a FOSM-aware MCP server and ACP agent into your Rails development
|
|
71
85
|
environment, giving coding agents (Claude Code, Codex, Copilot) runtime intelligence:
|