superkick 0.1.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 +7 -0
- data/CLA.md +91 -0
- data/CLAUDE.md +2226 -0
- data/CONTRIBUTING.md +104 -0
- data/LICENSE +108 -0
- data/LICENSE-COMMERCIAL.md +39 -0
- data/PLAN.md +161 -0
- data/README.md +1155 -0
- data/exe/superkick +6 -0
- data/lib/superkick/agent/runtime.rb +82 -0
- data/lib/superkick/agent/runtimes/local.rb +74 -0
- data/lib/superkick/agent/runtimes.rb +4 -0
- data/lib/superkick/agent.rb +209 -0
- data/lib/superkick/agent_store.rb +85 -0
- data/lib/superkick/attach/client.rb +245 -0
- data/lib/superkick/attach/protocol.rb +71 -0
- data/lib/superkick/attach/server.rb +371 -0
- data/lib/superkick/budget_checker.rb +120 -0
- data/lib/superkick/buffer/client.rb +91 -0
- data/lib/superkick/buffer/server.rb +127 -0
- data/lib/superkick/cli/agent.rb +524 -0
- data/lib/superkick/cli/completion.rb +591 -0
- data/lib/superkick/cli/goal.rb +71 -0
- data/lib/superkick/cli/mcp.rb +34 -0
- data/lib/superkick/cli/monitor.rb +47 -0
- data/lib/superkick/cli/notifier.rb +39 -0
- data/lib/superkick/cli/repository.rb +46 -0
- data/lib/superkick/cli/server.rb +106 -0
- data/lib/superkick/cli/setup.rb +166 -0
- data/lib/superkick/cli/spawner.rb +85 -0
- data/lib/superkick/cli/team.rb +407 -0
- data/lib/superkick/cli.rb +175 -0
- data/lib/superkick/client_registry.rb +30 -0
- data/lib/superkick/configuration.rb +178 -0
- data/lib/superkick/connection.rb +56 -0
- data/lib/superkick/control/client.rb +78 -0
- data/lib/superkick/control/reply.rb +43 -0
- data/lib/superkick/control/server.rb +1271 -0
- data/lib/superkick/cost_accumulator.rb +53 -0
- data/lib/superkick/cost_extractor.rb +65 -0
- data/lib/superkick/cost_poller.rb +70 -0
- data/lib/superkick/driver/profile_source.rb +134 -0
- data/lib/superkick/driver.rb +179 -0
- data/lib/superkick/drivers/claude_code.rb +110 -0
- data/lib/superkick/drivers/codex.rb +57 -0
- data/lib/superkick/drivers/copilot.rb +75 -0
- data/lib/superkick/drivers/gemini.rb +86 -0
- data/lib/superkick/drivers/goose.rb +74 -0
- data/lib/superkick/drivers.rb +16 -0
- data/lib/superkick/drop.rb +80 -0
- data/lib/superkick/drops.rb +76 -0
- data/lib/superkick/environment_executor.rb +90 -0
- data/lib/superkick/goal.rb +95 -0
- data/lib/superkick/goals/agent_exit.rb +41 -0
- data/lib/superkick/goals/agent_signal.rb +42 -0
- data/lib/superkick/goals/command.rb +103 -0
- data/lib/superkick/history_buffer.rb +38 -0
- data/lib/superkick/hosted/attach/bridge.rb +52 -0
- data/lib/superkick/hosted/attach/client.rb +208 -0
- data/lib/superkick/hosted/attach/relay.rb +313 -0
- data/lib/superkick/hosted/attach/relay_store.rb +48 -0
- data/lib/superkick/hosted/bridge.rb +263 -0
- data/lib/superkick/hosted/buffer/bridge.rb +42 -0
- data/lib/superkick/hosted/buffer/client.rb +63 -0
- data/lib/superkick/hosted/buffer/relay.rb +126 -0
- data/lib/superkick/hosted/buffer/relay_store.rb +42 -0
- data/lib/superkick/hosted/control/client.rb +84 -0
- data/lib/superkick/hosted/mcp_proxy.rb +144 -0
- data/lib/superkick/inject_handler.rb +24 -0
- data/lib/superkick/injection_guard.rb +26 -0
- data/lib/superkick/injection_queue.rb +177 -0
- data/lib/superkick/injector.rb +65 -0
- data/lib/superkick/input_buffer.rb +171 -0
- data/lib/superkick/integrations/bugsnag/README.md +98 -0
- data/lib/superkick/integrations/bugsnag/spawner.rb +307 -0
- data/lib/superkick/integrations/bugsnag/templates/error_opened.liquid +17 -0
- data/lib/superkick/integrations/bugsnag.rb +7 -0
- data/lib/superkick/integrations/circleci/README.md +75 -0
- data/lib/superkick/integrations/circleci/monitor.rb +185 -0
- data/lib/superkick/integrations/circleci/probe.rb +36 -0
- data/lib/superkick/integrations/circleci/templates/ci_failure.liquid +8 -0
- data/lib/superkick/integrations/circleci/templates/ci_success.liquid +1 -0
- data/lib/superkick/integrations/circleci.rb +8 -0
- data/lib/superkick/integrations/datadog/README.md +253 -0
- data/lib/superkick/integrations/datadog/alert_goal.rb +94 -0
- data/lib/superkick/integrations/datadog/alert_monitor.rb +163 -0
- data/lib/superkick/integrations/datadog/alert_spawner.rb +201 -0
- data/lib/superkick/integrations/datadog/notification_templates/default.liquid +10 -0
- data/lib/superkick/integrations/datadog/notifier.rb +294 -0
- data/lib/superkick/integrations/datadog/spawner.rb +201 -0
- data/lib/superkick/integrations/datadog/templates/alert_changed.liquid +8 -0
- data/lib/superkick/integrations/datadog/templates/alert_escalated.liquid +8 -0
- data/lib/superkick/integrations/datadog/templates/alert_recovered.liquid +14 -0
- data/lib/superkick/integrations/datadog/templates/alert_triggered.liquid +29 -0
- data/lib/superkick/integrations/datadog/templates/error_opened.liquid +15 -0
- data/lib/superkick/integrations/datadog.rb +14 -0
- data/lib/superkick/integrations/docker/README.md +256 -0
- data/lib/superkick/integrations/docker/client.rb +295 -0
- data/lib/superkick/integrations/docker/runtime.rb +218 -0
- data/lib/superkick/integrations/docker.rb +4 -0
- data/lib/superkick/integrations/git/repository_source.rb +66 -0
- data/lib/superkick/integrations/git/version_control.rb +119 -0
- data/lib/superkick/integrations/git.rb +8 -0
- data/lib/superkick/integrations/github/README.md +300 -0
- data/lib/superkick/integrations/github/check_failed_spawner.rb +199 -0
- data/lib/superkick/integrations/github/drops.rb +114 -0
- data/lib/superkick/integrations/github/goal.rb +135 -0
- data/lib/superkick/integrations/github/issue_goal.rb +104 -0
- data/lib/superkick/integrations/github/issue_spawner.rb +160 -0
- data/lib/superkick/integrations/github/monitor.rb +251 -0
- data/lib/superkick/integrations/github/probe.rb +30 -0
- data/lib/superkick/integrations/github/repository_source.rb +228 -0
- data/lib/superkick/integrations/github/templates/check_failed.liquid +10 -0
- data/lib/superkick/integrations/github/templates/ci_failure.liquid +5 -0
- data/lib/superkick/integrations/github/templates/ci_success.liquid +1 -0
- data/lib/superkick/integrations/github/templates/issue_opened.liquid +20 -0
- data/lib/superkick/integrations/github/templates/pr_comment.liquid +2 -0
- data/lib/superkick/integrations/github/templates/pr_review.liquid +4 -0
- data/lib/superkick/integrations/github.rb +16 -0
- data/lib/superkick/integrations/honeybadger/README.md +97 -0
- data/lib/superkick/integrations/honeybadger/notification_templates/default.liquid +8 -0
- data/lib/superkick/integrations/honeybadger/notifier.rb +250 -0
- data/lib/superkick/integrations/honeybadger/spawner.rb +214 -0
- data/lib/superkick/integrations/honeybadger/templates/error_opened.liquid +17 -0
- data/lib/superkick/integrations/honeybadger.rb +9 -0
- data/lib/superkick/integrations/shell/README.md +83 -0
- data/lib/superkick/integrations/shell/monitor.rb +87 -0
- data/lib/superkick/integrations/shell/templates/shell_alert.liquid +6 -0
- data/lib/superkick/integrations/shell/templates/shell_success.liquid +6 -0
- data/lib/superkick/integrations/shell.rb +7 -0
- data/lib/superkick/integrations/shortcut/README.md +193 -0
- data/lib/superkick/integrations/shortcut/drops.rb +91 -0
- data/lib/superkick/integrations/shortcut/monitor.rb +582 -0
- data/lib/superkick/integrations/shortcut/probe.rb +34 -0
- data/lib/superkick/integrations/shortcut/spawner.rb +264 -0
- data/lib/superkick/integrations/shortcut/templates/related_story_changed.liquid +6 -0
- data/lib/superkick/integrations/shortcut/templates/story_blocker.liquid +8 -0
- data/lib/superkick/integrations/shortcut/templates/story_comment.liquid +5 -0
- data/lib/superkick/integrations/shortcut/templates/story_description_changed.liquid +19 -0
- data/lib/superkick/integrations/shortcut/templates/story_owner_changed.liquid +10 -0
- data/lib/superkick/integrations/shortcut/templates/story_ready.liquid +41 -0
- data/lib/superkick/integrations/shortcut/templates/story_state_changed.liquid +9 -0
- data/lib/superkick/integrations/shortcut/templates/story_unblocked.liquid +5 -0
- data/lib/superkick/integrations/shortcut.rb +11 -0
- data/lib/superkick/integrations/slack/README.md +297 -0
- data/lib/superkick/integrations/slack/drops.rb +70 -0
- data/lib/superkick/integrations/slack/notifier.rb +426 -0
- data/lib/superkick/integrations/slack/spawner.rb +251 -0
- data/lib/superkick/integrations/slack/templates/default.liquid +17 -0
- data/lib/superkick/integrations/slack/templates/slack_reply.liquid +3 -0
- data/lib/superkick/integrations/slack/templates/spawn/slack_message.liquid +10 -0
- data/lib/superkick/integrations/slack/thread_monitor.rb +161 -0
- data/lib/superkick/integrations/slack.rb +12 -0
- data/lib/superkick/liquid.rb +129 -0
- data/lib/superkick/local/repository_source.rb +148 -0
- data/lib/superkick/mcp_server.rb +596 -0
- data/lib/superkick/monitor.rb +215 -0
- data/lib/superkick/notification_dispatcher.rb +280 -0
- data/lib/superkick/notifier.rb +173 -0
- data/lib/superkick/notifier_state_store.rb +55 -0
- data/lib/superkick/notifier_template.rb +121 -0
- data/lib/superkick/notifiers/command.rb +124 -0
- data/lib/superkick/notifiers/terminal_bell.rb +41 -0
- data/lib/superkick/output_logger.rb +54 -0
- data/lib/superkick/poller.rb +126 -0
- data/lib/superkick/process_runner.rb +87 -0
- data/lib/superkick/pty_proxy.rb +403 -0
- data/lib/superkick/registry.rb +75 -0
- data/lib/superkick/repository_source.rb +195 -0
- data/lib/superkick/server.rb +211 -0
- data/lib/superkick/session_recorder.rb +154 -0
- data/lib/superkick/setup.rb +160 -0
- data/lib/superkick/spawn/agent_spawner.rb +311 -0
- data/lib/superkick/spawn/approval_store.rb +113 -0
- data/lib/superkick/spawn/handler.rb +144 -0
- data/lib/superkick/spawn/injector.rb +119 -0
- data/lib/superkick/spawn/workflow_executor.rb +196 -0
- data/lib/superkick/spawn/workflow_validator.rb +77 -0
- data/lib/superkick/spawner.rb +67 -0
- data/lib/superkick/supervisor.rb +516 -0
- data/lib/superkick/team/artifact_store.rb +92 -0
- data/lib/superkick/team/log.rb +140 -0
- data/lib/superkick/team/log_entry_drop.rb +34 -0
- data/lib/superkick/team/log_monitor.rb +84 -0
- data/lib/superkick/team/log_notifier.rb +96 -0
- data/lib/superkick/team/log_store.rb +40 -0
- data/lib/superkick/template_filters.rb +24 -0
- data/lib/superkick/template_renderer.rb +223 -0
- data/lib/superkick/templates/team_log/planning_agent.liquid +38 -0
- data/lib/superkick/templates/team_log/team_digest.liquid +45 -0
- data/lib/superkick/templates/team_log/teammate_message.liquid +7 -0
- data/lib/superkick/templates/team_log/worker_kickoff.liquid +37 -0
- data/lib/superkick/templates/workflow/workflow_triggered.liquid +22 -0
- data/lib/superkick/version.rb +5 -0
- data/lib/superkick/version_control.rb +135 -0
- data/lib/superkick/yaml_config.rb +302 -0
- data/lib/superkick.rb +198 -0
- data/plan.md +267 -0
- metadata +404 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mcp"
|
|
4
|
+
|
|
5
|
+
module Superkick
|
|
6
|
+
# MCP stdio server — spawned by the AI CLI as a subprocess.
|
|
7
|
+
# Translates MCP tool calls into Control::Client requests to the server.
|
|
8
|
+
#
|
|
9
|
+
# Reads SUPERKICK_AGENT_ID from the environment (set by PtyProxy when spawning
|
|
10
|
+
# the AI CLI). All tools that need the agent identity use this automatically
|
|
11
|
+
# rather than requiring the AI to pass it as a parameter.
|
|
12
|
+
#
|
|
13
|
+
# Tools exposed:
|
|
14
|
+
# superkick_discover_monitors — list available monitor types and probe-detected configs
|
|
15
|
+
# superkick_discover_goals — list available goal types for worker agents
|
|
16
|
+
# superkick_add_monitor — add a monitor to this agent
|
|
17
|
+
# superkick_remove_monitor — remove a monitor from this agent
|
|
18
|
+
# superkick_signal_goal — signal agent goal status (completed, failed, errored, in_progress)
|
|
19
|
+
# superkick_status — report server/agent status
|
|
20
|
+
class McpServer
|
|
21
|
+
def self.start
|
|
22
|
+
new.run
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(control_client: nil)
|
|
26
|
+
@agent_id = ENV["SUPERKICK_AGENT_ID"]
|
|
27
|
+
raise "SUPERKICK_AGENT_ID must be set. The MCP server should be started via `superkick agent`." unless @agent_id
|
|
28
|
+
@control_client = control_client || Control.client_from
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def run
|
|
32
|
+
server = build_server
|
|
33
|
+
transport = MCP::Server::Transports::StdioTransport.new(server)
|
|
34
|
+
server.transport = transport
|
|
35
|
+
transport.open
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Build the MCP::Server with all tools defined. Exposed for integration
|
|
39
|
+
# testing — callers can use server.handle_json(request) directly without
|
|
40
|
+
# starting the stdio transport.
|
|
41
|
+
def build_server
|
|
42
|
+
server = MCP::Server.new(name: "superkick", version: Superkick::VERSION)
|
|
43
|
+
define_tools(server)
|
|
44
|
+
server
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def define_tools(server) # rubocop:disable Metrics/MethodLength
|
|
50
|
+
# Capture helpers as lambdas so tool blocks (which run with self = Tool class)
|
|
51
|
+
# can call back into the McpServer instance via closures.
|
|
52
|
+
request = method(:server_request)
|
|
53
|
+
agent_id = @agent_id
|
|
54
|
+
do_discover_monitors = method(:discover_monitors)
|
|
55
|
+
do_discover_goals = method(:discover_goals)
|
|
56
|
+
do_discover_notifiers = method(:discover_notifiers)
|
|
57
|
+
do_symbolize_keys = method(:symbolize_keys)
|
|
58
|
+
do_symbolize_keys_deep = method(:symbolize_keys_deep)
|
|
59
|
+
|
|
60
|
+
# MCP gem 0.7+ expects tool blocks to return MCP::Tool::Response, not plain strings.
|
|
61
|
+
text_response = ->(text) {
|
|
62
|
+
MCP::Tool::Response.new([{type: "text", text: text}])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
server.define_tool(
|
|
66
|
+
name: "superkick_discover_monitors",
|
|
67
|
+
description: "List all available Superkick monitor types, their descriptions, required configuration, " \
|
|
68
|
+
"and event types. Optionally runs environment probes to detect monitors that can be auto-configured " \
|
|
69
|
+
"for the current working directory (e.g. detecting the GitHub repo and branch from git remotes). " \
|
|
70
|
+
"Use this to discover what monitors you can start before calling superkick_add_monitor.",
|
|
71
|
+
input_schema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
run_probes: {
|
|
75
|
+
type: "boolean",
|
|
76
|
+
description: "Run environment probes to detect auto-configurable monitors (default: true)"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
) do |run_probes: true, **_|
|
|
81
|
+
text_response.call(do_discover_monitors.call(run_probes:))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
server.define_tool(
|
|
85
|
+
name: "superkick_discover_notifiers",
|
|
86
|
+
description: "List all available Superkick notifier types, their descriptions, and configuration options. " \
|
|
87
|
+
"Use this to discover what notifier types you can attach to agents or workers " \
|
|
88
|
+
"before calling superkick_add_notifier or superkick_spawn_worker.",
|
|
89
|
+
input_schema: {type: "object", properties: {}}
|
|
90
|
+
) do |**_|
|
|
91
|
+
text_response.call(do_discover_notifiers.call)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
server.define_tool(
|
|
95
|
+
name: "superkick_discover_goals",
|
|
96
|
+
description: "List all available Superkick goal types, their descriptions, and required configuration. " \
|
|
97
|
+
"Use this to understand what goal types you can assign to workers when calling superkick_spawn_worker.",
|
|
98
|
+
input_schema: {type: "object", properties: {}}
|
|
99
|
+
) do |**_|
|
|
100
|
+
text_response.call(do_discover_goals.call)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
server.define_tool(
|
|
104
|
+
name: "superkick_discover_repositories",
|
|
105
|
+
description: "List all configured repositories with their descriptions, tags, dependencies, " \
|
|
106
|
+
"and paths. Use this to discover which repositories are available for spawning workers " \
|
|
107
|
+
"or understanding the project landscape.",
|
|
108
|
+
input_schema: {type: "object", properties: {}}
|
|
109
|
+
) do |**_|
|
|
110
|
+
text = begin
|
|
111
|
+
result = request.call("discover_repositories")
|
|
112
|
+
result.success? ? JSON.pretty_generate(result.payload) : "Error: #{result.error_message}"
|
|
113
|
+
rescue Control::Client::ServerUnavailable => e
|
|
114
|
+
"Superkick server unavailable: #{e.message}"
|
|
115
|
+
end
|
|
116
|
+
text_response.call(text)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
server.define_tool(
|
|
120
|
+
name: "superkick_add_monitor",
|
|
121
|
+
description: "Add a named monitor to this agent.",
|
|
122
|
+
input_schema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
required: %w[monitor_name],
|
|
125
|
+
properties: {
|
|
126
|
+
monitor_name: {type: "string", description: "Unique instance name (e.g. 'github', 'disk_check')"},
|
|
127
|
+
monitor_type: {type: "string", description: "Monitor class type (e.g. 'shell'). Inferred from name if omitted."},
|
|
128
|
+
config: {type: "object", description: "Monitor-specific configuration"}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
) do |monitor_name:, monitor_type: nil, config: {}, **_|
|
|
132
|
+
text = begin
|
|
133
|
+
config = config.dup
|
|
134
|
+
config[:type] = monitor_type if monitor_type
|
|
135
|
+
result = request.call("add_monitor",
|
|
136
|
+
agent_id:,
|
|
137
|
+
monitor_name:,
|
|
138
|
+
config:)
|
|
139
|
+
result.success? ? "Monitor #{monitor_name} added." : "Error: #{result.error_message}"
|
|
140
|
+
rescue Control::Client::ServerUnavailable => e
|
|
141
|
+
"Superkick server unavailable: #{e.message}"
|
|
142
|
+
end
|
|
143
|
+
text_response.call(text)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
server.define_tool(
|
|
147
|
+
name: "superkick_remove_monitor",
|
|
148
|
+
description: "Remove a named monitor from this agent.",
|
|
149
|
+
input_schema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
required: %w[monitor_name],
|
|
152
|
+
properties: {
|
|
153
|
+
monitor_name: {type: "string", description: "The monitor instance name to remove"}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
) do |monitor_name:, **_|
|
|
157
|
+
text = begin
|
|
158
|
+
result = request.call("remove_monitor",
|
|
159
|
+
agent_id:,
|
|
160
|
+
monitor_name:)
|
|
161
|
+
result.success? ? "Monitor removed." : "Error: #{result.error_message}"
|
|
162
|
+
rescue Control::Client::ServerUnavailable => e
|
|
163
|
+
"Superkick server unavailable: #{e.message}"
|
|
164
|
+
end
|
|
165
|
+
text_response.call(text)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
server.define_tool(
|
|
169
|
+
name: "superkick_add_notifier",
|
|
170
|
+
description: "Add a named notifier to this agent. Per-agent notifiers fire " \
|
|
171
|
+
"alongside global notifiers for this agent's events only.",
|
|
172
|
+
input_schema: {
|
|
173
|
+
type: "object",
|
|
174
|
+
required: %w[notifier_name],
|
|
175
|
+
properties: {
|
|
176
|
+
notifier_name: {type: "string", description: "Unique instance name (e.g. 'slack_ops', 'datadog')"},
|
|
177
|
+
notifier_type: {type: "string", description: "Notifier class type (e.g. 'slack'). Inferred from name if omitted."},
|
|
178
|
+
config: {type: "object", description: "Notifier-specific configuration"}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
) do |notifier_name:, notifier_type: nil, config: {}, **_|
|
|
182
|
+
text = begin
|
|
183
|
+
config = do_symbolize_keys.call(config.dup)
|
|
184
|
+
config[:type] = notifier_type if notifier_type
|
|
185
|
+
result = request.call("add_notifier",
|
|
186
|
+
agent_id:,
|
|
187
|
+
notifier_name:,
|
|
188
|
+
config:)
|
|
189
|
+
result.success? ? "Notifier #{notifier_name} added." : "Error: #{result.error_message}"
|
|
190
|
+
rescue Control::Client::ServerUnavailable => e
|
|
191
|
+
"Superkick server unavailable: #{e.message}"
|
|
192
|
+
end
|
|
193
|
+
text_response.call(text)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
server.define_tool(
|
|
197
|
+
name: "superkick_remove_notifier",
|
|
198
|
+
description: "Remove a named notifier from this agent.",
|
|
199
|
+
input_schema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
required: %w[notifier_name],
|
|
202
|
+
properties: {
|
|
203
|
+
notifier_name: {type: "string", description: "The notifier instance name to remove"}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
) do |notifier_name:, **_|
|
|
207
|
+
text = begin
|
|
208
|
+
result = request.call("remove_notifier",
|
|
209
|
+
agent_id:,
|
|
210
|
+
notifier_name:)
|
|
211
|
+
result.success? ? "Notifier removed." : "Error: #{result.error_message}"
|
|
212
|
+
rescue Control::Client::ServerUnavailable => e
|
|
213
|
+
"Superkick server unavailable: #{e.message}"
|
|
214
|
+
end
|
|
215
|
+
text_response.call(text)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
server.define_tool(
|
|
219
|
+
name: "superkick_signal_goal",
|
|
220
|
+
description: "Signal the status of this agent's goal. Use this to report progress, " \
|
|
221
|
+
"completion, or failure of your assigned task. Terminal statuses (completed, failed) " \
|
|
222
|
+
"tell Superkick to clean up resources and optionally trigger downstream workflows.",
|
|
223
|
+
input_schema: {
|
|
224
|
+
type: "object",
|
|
225
|
+
required: %w[status],
|
|
226
|
+
properties: {
|
|
227
|
+
status: {
|
|
228
|
+
type: "string",
|
|
229
|
+
enum: %w[completed failed errored in_progress],
|
|
230
|
+
description: "Goal status: 'completed' = task succeeded (terminal), " \
|
|
231
|
+
"'failed' = irrecoverable failure (terminal), " \
|
|
232
|
+
"'errored' = potentially recoverable issue, " \
|
|
233
|
+
"'in_progress' = actively working"
|
|
234
|
+
},
|
|
235
|
+
message: {type: "string", description: "Optional message describing the outcome"},
|
|
236
|
+
summary: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Brief summary of what happened. For failures, explain what went wrong " \
|
|
239
|
+
"and what was tried. This summary is forwarded to retry agents in workflow chains."
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
) do |status:, summary: nil, **_|
|
|
244
|
+
text = begin
|
|
245
|
+
result = request.call("signal_goal",
|
|
246
|
+
agent_id:,
|
|
247
|
+
status:,
|
|
248
|
+
summary:)
|
|
249
|
+
result.success? ? "Signal received: #{status}" : "Error: #{result.error_message}"
|
|
250
|
+
rescue Control::Client::ServerUnavailable => e
|
|
251
|
+
"Superkick server unavailable: #{e.message}"
|
|
252
|
+
end
|
|
253
|
+
text_response.call(text)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
server.define_tool(
|
|
257
|
+
name: "superkick_report_cost",
|
|
258
|
+
description: "Report token usage and cost for this agent session. " \
|
|
259
|
+
"Call this periodically or at task completion to enable cost tracking. " \
|
|
260
|
+
"Values should be cumulative totals (not incremental deltas).",
|
|
261
|
+
input_schema: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
tokens_in: {type: "integer", description: "Total input tokens used so far"},
|
|
265
|
+
tokens_out: {type: "integer", description: "Total output tokens used so far"},
|
|
266
|
+
cost_usd: {type: "number", description: "Total cost in USD so far"}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
) do |tokens_in: nil, tokens_out: nil, cost_usd: nil, **_|
|
|
270
|
+
text = begin
|
|
271
|
+
result = request.call("report_cost",
|
|
272
|
+
agent_id:,
|
|
273
|
+
tokens_in:,
|
|
274
|
+
tokens_out:,
|
|
275
|
+
cost_usd:,
|
|
276
|
+
source: :mcp_report)
|
|
277
|
+
result.success? ? "Cost recorded." : "Error: #{result.error_message}"
|
|
278
|
+
rescue Control::Client::ServerUnavailable => e
|
|
279
|
+
"Superkick server unavailable: #{e.message}"
|
|
280
|
+
end
|
|
281
|
+
text_response.call(text)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
server.define_tool(
|
|
285
|
+
name: "superkick_status",
|
|
286
|
+
description: "Report Superkick server status and active agents.",
|
|
287
|
+
input_schema: {type: "object", properties: {}}
|
|
288
|
+
) do |**_|
|
|
289
|
+
text = begin
|
|
290
|
+
ping = begin
|
|
291
|
+
request.call("ping")
|
|
292
|
+
rescue Control::Client::ServerUnavailable
|
|
293
|
+
nil
|
|
294
|
+
end
|
|
295
|
+
unless ping&.success?
|
|
296
|
+
next text_response.call("Superkick server is not running.")
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
agents_resp = request.call("list_agents")
|
|
300
|
+
agents = agents_resp[:agents] || []
|
|
301
|
+
|
|
302
|
+
if agents.empty?
|
|
303
|
+
"Superkick server running (v#{ping[:version]}). No active agents."
|
|
304
|
+
else
|
|
305
|
+
lines = agents.map do |s|
|
|
306
|
+
buf = s[:has_buffer] ? "pty-connected" : "no-pty"
|
|
307
|
+
line = " agent #{s[:agent_id]}: #{s[:monitor_count]} monitor(s), #{buf}"
|
|
308
|
+
if s[:cost] && s[:cost][:total_cost_usd] > 0
|
|
309
|
+
line += " cost=$#{s[:cost][:total_cost_usd].round(2)}"
|
|
310
|
+
end
|
|
311
|
+
line
|
|
312
|
+
end
|
|
313
|
+
"Superkick server running (v#{ping[:version]}).\n#{lines.join("\n")}"
|
|
314
|
+
end
|
|
315
|
+
rescue Control::Client::ServerUnavailable => e
|
|
316
|
+
"Superkick server unavailable: #{e.message}"
|
|
317
|
+
end
|
|
318
|
+
text_response.call(text)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# ── Team tools ────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
server.define_tool(
|
|
324
|
+
name: "superkick_post_update",
|
|
325
|
+
description: "Post an update or send a message. Without target_agent_id, " \
|
|
326
|
+
"broadcasts to all configured notification channels (Slack, team log, etc.). " \
|
|
327
|
+
"With target_agent_id, sends a direct message injected into that teammate's prompt. " \
|
|
328
|
+
"Use kind to categorize: 'blocker' gets high priority routing.",
|
|
329
|
+
input_schema: {
|
|
330
|
+
type: "object",
|
|
331
|
+
required: %w[message],
|
|
332
|
+
properties: {
|
|
333
|
+
message: {type: "string", description: "The update or message text"},
|
|
334
|
+
kind: {
|
|
335
|
+
type: "string",
|
|
336
|
+
enum: %w[status decision blocker progress completed failed],
|
|
337
|
+
description: "Type of update (for broadcast mode)"
|
|
338
|
+
},
|
|
339
|
+
target_agent_id: {
|
|
340
|
+
type: "string",
|
|
341
|
+
description: "Send a direct message to this teammate instead of broadcasting. " \
|
|
342
|
+
"Both agents must be on the same team."
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
) do |message:, kind: nil, target_agent_id: nil, **_|
|
|
347
|
+
text = begin
|
|
348
|
+
result = request.call("post_update",
|
|
349
|
+
agent_id:,
|
|
350
|
+
message:,
|
|
351
|
+
kind:,
|
|
352
|
+
target_agent_id:)
|
|
353
|
+
result.success? ? "Update posted." : "Error: #{result.error_message}"
|
|
354
|
+
rescue Control::Client::ServerUnavailable => e
|
|
355
|
+
"Superkick server unavailable: #{e.message}"
|
|
356
|
+
end
|
|
357
|
+
text_response.call(text)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
server.define_tool(
|
|
361
|
+
name: "superkick_team_status",
|
|
362
|
+
description: "Get the current status of your team. Returns each teammate's " \
|
|
363
|
+
"latest update and any unresolved blockers.",
|
|
364
|
+
input_schema: {
|
|
365
|
+
type: "object",
|
|
366
|
+
properties: {
|
|
367
|
+
full_log: {
|
|
368
|
+
type: "boolean",
|
|
369
|
+
description: "If true, returns the full team log instead of just the summary",
|
|
370
|
+
default: false
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
) do |full_log: false, **_|
|
|
375
|
+
text = begin
|
|
376
|
+
result = request.call("team_status",
|
|
377
|
+
agent_id:,
|
|
378
|
+
full_log:)
|
|
379
|
+
result.success? ? JSON.pretty_generate(result.payload) : "Error: #{result.error_message}"
|
|
380
|
+
rescue Control::Client::ServerUnavailable => e
|
|
381
|
+
"Superkick server unavailable: #{e.message}"
|
|
382
|
+
end
|
|
383
|
+
text_response.call(text)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
server.define_tool(
|
|
387
|
+
name: "superkick_list_teammates",
|
|
388
|
+
description: "List all agents on your team, including their roles, " \
|
|
389
|
+
"current status, and what they're working on.",
|
|
390
|
+
input_schema: {type: "object", properties: {}}
|
|
391
|
+
) do |**_|
|
|
392
|
+
text = begin
|
|
393
|
+
result = request.call("list_teammates", agent_id:)
|
|
394
|
+
result.success? ? JSON.pretty_generate(result.payload) : "Error: #{result.error_message}"
|
|
395
|
+
rescue Control::Client::ServerUnavailable => e
|
|
396
|
+
"Superkick server unavailable: #{e.message}"
|
|
397
|
+
end
|
|
398
|
+
text_response.call(text)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
server.define_tool(
|
|
402
|
+
name: "superkick_spawn_worker",
|
|
403
|
+
description: "Spawn a worker agent to work on a specific repository as " \
|
|
404
|
+
"part of your team. The worker will be set up with a git worktree and " \
|
|
405
|
+
"given your instructions as its kickoff prompt.",
|
|
406
|
+
input_schema: {
|
|
407
|
+
type: "object",
|
|
408
|
+
required: %w[repository task agent_suffix],
|
|
409
|
+
properties: {
|
|
410
|
+
repository: {type: "string", description: "Name of the repository from the repository registry"},
|
|
411
|
+
task: {type: "string", description: "Detailed instructions for what the worker should do"},
|
|
412
|
+
agent_suffix: {
|
|
413
|
+
type: "string",
|
|
414
|
+
description: "Short suffix for the agent ID (e.g. 'api', 'web'). " \
|
|
415
|
+
"The full ID will be {team_id}-{suffix}."
|
|
416
|
+
},
|
|
417
|
+
goal: {
|
|
418
|
+
type: "object",
|
|
419
|
+
description: "Goal configuration for the worker. Defaults to agent_signal.",
|
|
420
|
+
properties: {type: {type: "string"}}
|
|
421
|
+
},
|
|
422
|
+
role: {
|
|
423
|
+
type: "string",
|
|
424
|
+
description: "Short label describing the worker's function (e.g. 'API migration', 'Test writer')"
|
|
425
|
+
},
|
|
426
|
+
depends_on: {
|
|
427
|
+
type: "array",
|
|
428
|
+
items: {type: "string"},
|
|
429
|
+
description: "Agent IDs this worker depends on. The worker's " \
|
|
430
|
+
"instructions will note these dependencies."
|
|
431
|
+
},
|
|
432
|
+
monitors: {
|
|
433
|
+
type: "object",
|
|
434
|
+
description: "Named monitors to attach to the worker. " \
|
|
435
|
+
"Each key is a monitor name, each value is its configuration " \
|
|
436
|
+
"(must include 'type' if the name doesn't match a registered monitor type). " \
|
|
437
|
+
"Use superkick_discover_monitors to see available types.",
|
|
438
|
+
additionalProperties: {type: "object"}
|
|
439
|
+
},
|
|
440
|
+
notifiers: {
|
|
441
|
+
type: "object",
|
|
442
|
+
description: "Named notifiers to attach to the worker. " \
|
|
443
|
+
"Each key is a notifier name, each value is its configuration " \
|
|
444
|
+
"(must include 'type'). These fire alongside global notifiers for " \
|
|
445
|
+
"this worker's events only. Use superkick_discover_notifiers to see available types.",
|
|
446
|
+
additionalProperties: {type: "object"}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
) do |repository:, task:, agent_suffix:, goal: nil, role: nil, depends_on: nil, monitors: nil, notifiers: nil, **_|
|
|
451
|
+
text = begin
|
|
452
|
+
goal = goal.transform_keys(&:to_sym) if goal
|
|
453
|
+
monitors = do_symbolize_keys_deep.call(monitors) if monitors
|
|
454
|
+
notifiers = do_symbolize_keys_deep.call(notifiers) if notifiers
|
|
455
|
+
|
|
456
|
+
result = request.call("spawn_worker",
|
|
457
|
+
agent_id:,
|
|
458
|
+
repository:,
|
|
459
|
+
task:,
|
|
460
|
+
agent_suffix:,
|
|
461
|
+
role:,
|
|
462
|
+
goal:,
|
|
463
|
+
depends_on:,
|
|
464
|
+
monitors:,
|
|
465
|
+
notifiers:)
|
|
466
|
+
result.success? ? "Worker spawned: #{result[:agent_id]}" : "Error: #{result.error_message}"
|
|
467
|
+
rescue Control::Client::ServerUnavailable => e
|
|
468
|
+
"Superkick server unavailable: #{e.message}"
|
|
469
|
+
end
|
|
470
|
+
text_response.call(text)
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# ── Artifact tools ──────────────────────────────────────────────
|
|
474
|
+
|
|
475
|
+
server.define_tool(
|
|
476
|
+
name: "superkick_publish_artifact",
|
|
477
|
+
description: "Publish a named artifact to your team's shared store. Use this to share " \
|
|
478
|
+
"implementation plans, summaries, API contracts, or any structured data with teammates. " \
|
|
479
|
+
"Artifacts are visible to the entire team. Publishing to an existing name updates it.",
|
|
480
|
+
input_schema: {
|
|
481
|
+
type: "object",
|
|
482
|
+
required: %w[name content],
|
|
483
|
+
properties: {
|
|
484
|
+
name: {type: "string", description: "Artifact name (e.g. 'implementation-plan', 'api-contract')"},
|
|
485
|
+
content: {type: "string", description: "The artifact content (text, JSON, YAML, etc.)"}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
) do |name:, content:, **_|
|
|
489
|
+
text = begin
|
|
490
|
+
result = request.call("publish_artifact",
|
|
491
|
+
agent_id:,
|
|
492
|
+
name:,
|
|
493
|
+
content:)
|
|
494
|
+
result.success? ? "Artifact '#{name}' published." : "Error: #{result.error_message}"
|
|
495
|
+
rescue Control::Client::ServerUnavailable => e
|
|
496
|
+
"Superkick server unavailable: #{e.message}"
|
|
497
|
+
end
|
|
498
|
+
text_response.call(text)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
server.define_tool(
|
|
502
|
+
name: "superkick_read_artifact",
|
|
503
|
+
description: "Read an artifact published by a teammate. Use superkick_list_artifacts " \
|
|
504
|
+
"to discover available artifacts first.",
|
|
505
|
+
input_schema: {
|
|
506
|
+
type: "object",
|
|
507
|
+
required: %w[author name],
|
|
508
|
+
properties: {
|
|
509
|
+
author: {type: "string", description: "The agent ID that published the artifact"},
|
|
510
|
+
name: {type: "string", description: "The artifact name"}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
) do |author:, name:, **_|
|
|
514
|
+
text = begin
|
|
515
|
+
result = request.call("read_artifact",
|
|
516
|
+
agent_id:,
|
|
517
|
+
author:,
|
|
518
|
+
name:)
|
|
519
|
+
result.success? ? JSON.pretty_generate(result.payload) : "Error: #{result.error_message}"
|
|
520
|
+
rescue Control::Client::ServerUnavailable => e
|
|
521
|
+
"Superkick server unavailable: #{e.message}"
|
|
522
|
+
end
|
|
523
|
+
text_response.call(text)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
server.define_tool(
|
|
527
|
+
name: "superkick_list_artifacts",
|
|
528
|
+
description: "List artifacts published by your team. Returns metadata (name, author, " \
|
|
529
|
+
"timestamps) without content. Optionally filter by author.",
|
|
530
|
+
input_schema: {
|
|
531
|
+
type: "object",
|
|
532
|
+
properties: {
|
|
533
|
+
author: {type: "string", description: "Filter by author agent ID (optional)"}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
) do |author: nil, **_|
|
|
537
|
+
text = begin
|
|
538
|
+
result = request.call("list_artifacts",
|
|
539
|
+
agent_id:,
|
|
540
|
+
author:)
|
|
541
|
+
result.success? ? JSON.pretty_generate(result.payload) : "Error: #{result.error_message}"
|
|
542
|
+
rescue Control::Client::ServerUnavailable => e
|
|
543
|
+
"Superkick server unavailable: #{e.message}"
|
|
544
|
+
end
|
|
545
|
+
text_response.call(text)
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def discover_monitors(run_probes:)
|
|
550
|
+
result = server_request("discover_monitors", agent_id: run_probes ? @agent_id : nil)
|
|
551
|
+
if result.success?
|
|
552
|
+
JSON.pretty_generate(result.payload)
|
|
553
|
+
else
|
|
554
|
+
"Error: #{result.error_message}"
|
|
555
|
+
end
|
|
556
|
+
rescue Control::Client::ServerUnavailable => e
|
|
557
|
+
"Superkick server unavailable: #{e.message}"
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def discover_goals
|
|
561
|
+
result = server_request("discover_goals")
|
|
562
|
+
if result.success?
|
|
563
|
+
JSON.pretty_generate(result.payload)
|
|
564
|
+
else
|
|
565
|
+
"Error: #{result.error_message}"
|
|
566
|
+
end
|
|
567
|
+
rescue Control::Client::ServerUnavailable => e
|
|
568
|
+
"Superkick server unavailable: #{e.message}"
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def discover_notifiers
|
|
572
|
+
result = server_request("discover_notifiers")
|
|
573
|
+
if result.success?
|
|
574
|
+
JSON.pretty_generate(result.payload)
|
|
575
|
+
else
|
|
576
|
+
"Error: #{result.error_message}"
|
|
577
|
+
end
|
|
578
|
+
rescue Control::Client::ServerUnavailable => e
|
|
579
|
+
"Superkick server unavailable: #{e.message}"
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def symbolize_keys(hash)
|
|
583
|
+
hash.transform_keys(&:to_sym)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def symbolize_keys_deep(hash)
|
|
587
|
+
hash.each_with_object({}) do |(k, v), result|
|
|
588
|
+
result[k.to_sym] = v.is_a?(Hash) ? symbolize_keys_deep(v) : v
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def server_request(command, **params)
|
|
593
|
+
@control_client.request(command, **params)
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
end
|