diffbroker 0.9.0 → 0.11.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 +4 -4
- data/lib/diffbroker/agents/base.rb +3 -0
- data/lib/diffbroker/agents/claude.rb +6 -1
- data/lib/diffbroker/daemon.rb +18 -1
- data/lib/diffbroker/plan_usage.rb +114 -0
- data/lib/diffbroker/session_ledger.rb +47 -0
- data/lib/diffbroker/task_runner.rb +32 -6
- data/lib/diffbroker/version.rb +1 -1
- data/lib/diffbroker.rb +2 -0
- data/plugin/.claude-plugin/plugin.json +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5edf0d3084adb3e7b1e8a9f55dda5062efa3a5207d8f4ca7d7860141acef3ec7
|
|
4
|
+
data.tar.gz: 85899028c0a4d827e3df60c530298f9713e4a33e4ccf5ec4397fae0d58a6c37a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b7ca4efd11472c69863a386ebf10336402eac96f998a8665457f20a7f6b395af3a3814925915e1881d0f018c906344a0eccd45cdd0d9557fc4f44fc0a3891be
|
|
7
|
+
data.tar.gz: d763d542d2a6c27adad2bf78cf3b6f0bb1488b0f384bb90fef6990b27c142af472078409f5886be43c92fe6df7b158e847547720018f28ce0efe57ab7060b4dc
|
|
@@ -16,6 +16,9 @@ module Diffbroker
|
|
|
16
16
|
# The run's lines for the user's log: what the model said and the tools it called. Subclasses override.
|
|
17
17
|
def parse_log(_line) = []
|
|
18
18
|
|
|
19
|
+
# Whether a chat message can continue this agent's earlier session (task["resume_session_id"]).
|
|
20
|
+
def resumes? = false
|
|
21
|
+
|
|
19
22
|
def env(token, task_id) = { TOKEN_ENV => token, "DIFFBROKER_TASK_ID" => task_id }
|
|
20
23
|
|
|
21
24
|
private
|
|
@@ -27,12 +27,17 @@ module Diffbroker
|
|
|
27
27
|
|
|
28
28
|
def name = "claude_code"
|
|
29
29
|
|
|
30
|
+
def resumes? = true
|
|
31
|
+
|
|
30
32
|
def autonomous_command(task, mcp_config:)
|
|
31
33
|
args = task["read_only"] ? READ_ONLY_ARGS : @config.claude_args
|
|
32
|
-
[ @config.claude_command, "-p", task.fetch("prompt"), "--output-format", "stream-json", "--verbose", *args,
|
|
34
|
+
[ @config.claude_command, "-p", task.fetch("prompt"), *resume(task), "--output-format", "stream-json", "--verbose", *args,
|
|
33
35
|
"--mcp-config", mcp_config, *strict(task), "--plugin-dir", @plugin_dir ]
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
# The permissions above are passed again: a resumed session keeps its conversation, not its sandbox.
|
|
39
|
+
def resume(task) = task["resume_session_id"] ? [ "--resume", task["resume_session_id"] ] : []
|
|
40
|
+
|
|
36
41
|
def terminal_command(task, mcp_config:)
|
|
37
42
|
[ @config.claude_command, "--mcp-config", mcp_config, *strict(task), "--plugin-dir", @plugin_dir ]
|
|
38
43
|
end
|
data/lib/diffbroker/daemon.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Diffbroker
|
|
|
11
11
|
class Rejected < Error; end
|
|
12
12
|
|
|
13
13
|
RESCAN_EVERY = 300
|
|
14
|
+
USAGE_EVERY = 300
|
|
14
15
|
MAX_BACKOFF = 60
|
|
15
16
|
BATCH = 50
|
|
16
17
|
# Spec §5.2 / main §18.4: nothing else about an observed agent ever leaves the machine.
|
|
@@ -26,6 +27,7 @@ module Diffbroker
|
|
|
26
27
|
new(scanner: config.repo_scanner, roots_changed: roots_changed(config), device_roots: config.device_roots, ignored_roots: config.ignored_roots,
|
|
27
28
|
client_factory: -> { CableClient.new(url: config.cable_url, token: config.device_token, origin: config.server) },
|
|
28
29
|
task_runner_factory: ->(report) { TaskRunner.new(config:, report:, out:) }, out:, relay:, process_scanner:, agent: config.agent,
|
|
30
|
+
plan_usage: PlanUsage.new(agent: config.agent),
|
|
29
31
|
lifecycle: Lifecycle.new(dir: config.dir, server: config.server), reaper: Reaper.new(root: config.worktree_root))
|
|
30
32
|
end
|
|
31
33
|
|
|
@@ -54,7 +56,7 @@ module Diffbroker
|
|
|
54
56
|
|
|
55
57
|
def initialize(scanner:, client_factory:, task_runner_factory:, out: $stdout, sleeper: ->(seconds) { sleep(seconds) },
|
|
56
58
|
clock: -> { Time.now }, spawner: ->(&block) { Thread.new(&block) }, relay: nil, process_scanner: nil, lifecycle: nil,
|
|
57
|
-
maintenance: Maintenance.new, roots_changed: nil, device_roots: [], ignored_roots: [], agent: "claude", reaper: nil)
|
|
59
|
+
maintenance: Maintenance.new, roots_changed: nil, device_roots: [], ignored_roots: [], agent: "claude", reaper: nil, plan_usage: nil)
|
|
58
60
|
@scanner = scanner
|
|
59
61
|
@device_roots = device_roots
|
|
60
62
|
@ignored_roots = ignored_roots
|
|
@@ -71,6 +73,7 @@ module Diffbroker
|
|
|
71
73
|
@maintenance = maintenance
|
|
72
74
|
@agent = agent
|
|
73
75
|
@reaper = reaper
|
|
76
|
+
@plan_usage = plan_usage
|
|
74
77
|
@repositories = []
|
|
75
78
|
@running = Set.new
|
|
76
79
|
@running_lock = Mutex.new
|
|
@@ -123,6 +126,7 @@ module Diffbroker
|
|
|
123
126
|
@backoff = 1
|
|
124
127
|
@rejections = 0
|
|
125
128
|
rescan(client)
|
|
129
|
+
@usage_at = nil
|
|
126
130
|
client.perform("hello", "version" => VERSION, "platform" => Diffbroker.platform,
|
|
127
131
|
"service" => @lifecycle ? @lifecycle.service_installed? : false, "agent" => @agent)
|
|
128
132
|
@out.puts "Connected. #{@repositories.size} repositories reported."
|
|
@@ -131,6 +135,7 @@ module Diffbroker
|
|
|
131
135
|
client.perform("heartbeat", "version" => VERSION)
|
|
132
136
|
rescan(client) if @scanned_at.nil? || @clock.call - @scanned_at >= RESCAN_EVERY
|
|
133
137
|
observe(client, @process_scanner.scan) if @process_scanner
|
|
138
|
+
report_usage(client)
|
|
134
139
|
end
|
|
135
140
|
client.on_poll { observe(client, @relay.drain) if @relay }
|
|
136
141
|
client.on_message { |message| handle(client, message) }
|
|
@@ -143,6 +148,18 @@ module Diffbroker
|
|
|
143
148
|
client.perform("repositories", "repositories" => @repositories)
|
|
144
149
|
end
|
|
145
150
|
|
|
151
|
+
# The plan limits change slowly and reading them can wait on the network, so: every few minutes, off the tick's thread.
|
|
152
|
+
def report_usage(client)
|
|
153
|
+
return unless @plan_usage
|
|
154
|
+
return if @usage_at && @clock.call - @usage_at < USAGE_EVERY
|
|
155
|
+
|
|
156
|
+
@usage_at = @clock.call
|
|
157
|
+
@spawner.call do
|
|
158
|
+
usage = @plan_usage.read
|
|
159
|
+
client.perform("usage", usage) if usage
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
146
163
|
def observe(client, events)
|
|
147
164
|
clean = Array(events).filter_map { _1.is_a?(Hash) ? _1.slice(*OBSERVATION_FIELDS) : nil }
|
|
148
165
|
clean.each_slice(BATCH) { client.perform("observe", "events" => _1) }
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "time"
|
|
6
|
+
|
|
7
|
+
module Diffbroker
|
|
8
|
+
# How much of the user's own plan limits the agent has used: Claude Code's 5-hour and weekly limits, Codex's rate-limit
|
|
9
|
+
# windows. Read on this machine with the user's own login and reported as plain percentages and reset times; no token,
|
|
10
|
+
# prompt or file ever leaves it. Every reader answers nil when it cannot tell (not logged in, no sessions, no network).
|
|
11
|
+
class PlanUsage
|
|
12
|
+
CLAUDE_URL = URI("https://api.anthropic.com/api/oauth/usage")
|
|
13
|
+
CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials"
|
|
14
|
+
MAX_WINDOWS = 2
|
|
15
|
+
# A window's key by its length in minutes: the rings and bars name them.
|
|
16
|
+
KEYS = { 300 => "session", 10_080 => "weekly", 43_200 => "monthly" }.freeze
|
|
17
|
+
CODEX_TAIL_BYTES = 400_000
|
|
18
|
+
|
|
19
|
+
def initialize(agent:, home: Dir.home, http: nil, keychain: nil, clock: -> { Time.now })
|
|
20
|
+
@agent = agent
|
|
21
|
+
@home = home
|
|
22
|
+
@http = http || method(:fetch)
|
|
23
|
+
@keychain = keychain || method(:read_keychain)
|
|
24
|
+
@clock = clock
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# => { "agent" => "claude", "windows" => [{ "key", "percent", "resets_at" }] } or nil
|
|
28
|
+
def read
|
|
29
|
+
windows = @agent == "codex" ? codex_windows : claude_windows
|
|
30
|
+
windows = Array(windows).first(MAX_WINDOWS)
|
|
31
|
+
{ "agent" => @agent, "windows" => windows } unless windows.empty?
|
|
32
|
+
rescue StandardError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def claude_windows
|
|
39
|
+
token = claude_token
|
|
40
|
+
return unless token
|
|
41
|
+
|
|
42
|
+
body = JSON.parse(@http.call(CLAUDE_URL, token))
|
|
43
|
+
[ [ "five_hour", "session" ], [ "seven_day", "weekly" ] ].filter_map do |field, key|
|
|
44
|
+
window = body[field]
|
|
45
|
+
next unless window.is_a?(Hash) && window["utilization"].is_a?(Numeric)
|
|
46
|
+
|
|
47
|
+
{ "key" => key, "percent" => window["utilization"].clamp(0, 100).round(1), "resets_at" => Time.parse(window["resets_at"].to_s).to_i }
|
|
48
|
+
rescue ArgumentError
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def claude_token
|
|
54
|
+
raw = @keychain.call || credentials_file
|
|
55
|
+
JSON.parse(raw.to_s).dig("claudeAiOauth", "accessToken")
|
|
56
|
+
rescue JSON::ParserError
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def credentials_file
|
|
61
|
+
path = File.join(@home, ".claude", ".credentials.json")
|
|
62
|
+
File.read(path) if File.file?(path)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def read_keychain
|
|
66
|
+
return unless RUBY_PLATFORM.include?("darwin")
|
|
67
|
+
|
|
68
|
+
out = IO.popen([ "security", "find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-w" ], err: File::NULL, &:read)
|
|
69
|
+
out.to_s.strip.then { _1.empty? ? nil : _1 }
|
|
70
|
+
rescue SystemCallError
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def fetch(uri, token)
|
|
75
|
+
request = Net::HTTP::Get.new(uri, "Authorization" => "Bearer #{token}", "anthropic-beta" => "oauth-2025-04-20", "Accept" => "application/json")
|
|
76
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 5, read_timeout: 8) { _1.request(request) }
|
|
77
|
+
raise Error, "usage #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
|
78
|
+
|
|
79
|
+
response.body
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Codex writes its limits into every session's log; the newest line of the newest session is the current state.
|
|
83
|
+
def codex_windows
|
|
84
|
+
file = Dir.glob(File.join(@home, ".codex", "sessions", "*", "*", "*", "rollout-*.jsonl")).max_by { File.mtime(_1) }
|
|
85
|
+
return unless file
|
|
86
|
+
|
|
87
|
+
line = tail(file).lines.reverse.find { _1.include?('"rate_limits"') }
|
|
88
|
+
limits = line && JSON.parse(line).dig("payload", "rate_limits")
|
|
89
|
+
return unless limits.is_a?(Hash)
|
|
90
|
+
|
|
91
|
+
now = @clock.call.to_i
|
|
92
|
+
windows = %w[primary secondary].filter_map { |name| codex_window(limits[name], now) }
|
|
93
|
+
windows.sort_by { _1["minutes"] }.map { _1.except("minutes") }
|
|
94
|
+
rescue JSON::ParserError
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def codex_window(window, now)
|
|
99
|
+
return unless window.is_a?(Hash) && window["used_percent"].is_a?(Numeric) && window["resets_at"].is_a?(Integer)
|
|
100
|
+
|
|
101
|
+
minutes = window["window_minutes"].to_i
|
|
102
|
+
# A window that already reset reports the old percentage until Codex runs again: it is empty now.
|
|
103
|
+
percent = window["resets_at"] <= now ? 0 : window["used_percent"].clamp(0, 100).round(1)
|
|
104
|
+
{ "key" => KEYS.fetch(minutes, minutes >= 10_080 ? "weekly" : "session"), "percent" => percent, "resets_at" => window["resets_at"], "minutes" => minutes }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def tail(path)
|
|
108
|
+
File.open(path) do |file|
|
|
109
|
+
file.seek([ file.size - CODEX_TAIL_BYTES, 0 ].max)
|
|
110
|
+
file.read.to_s
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Diffbroker
|
|
7
|
+
# The agent sessions this companion started itself. The server offers to resume a chat's earlier session by id, but it is
|
|
8
|
+
# only ever resumed here when this machine made it: a server (or anyone who could fake its message) cannot make an agent
|
|
9
|
+
# continue another conversation of the user's, such as an interactive session in their own terminal.
|
|
10
|
+
class SessionLedger
|
|
11
|
+
FILE = "sessions.json"
|
|
12
|
+
LIMIT = 500
|
|
13
|
+
ID = /\A\w[\w-]{0,99}\z/
|
|
14
|
+
|
|
15
|
+
def initialize(state_dir:)
|
|
16
|
+
@path = File.join(state_dir, FILE)
|
|
17
|
+
@lock = Mutex.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def remember(id)
|
|
21
|
+
return unless id.to_s.match?(ID)
|
|
22
|
+
|
|
23
|
+
@lock.synchronize do
|
|
24
|
+
ids = read
|
|
25
|
+
next if ids.include?(id)
|
|
26
|
+
|
|
27
|
+
write((ids + [ id ]).last(LIMIT))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def known?(id) = id.to_s.match?(ID) && @lock.synchronize { read.include?(id) }
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def read
|
|
36
|
+
data = JSON.parse(File.read(@path))
|
|
37
|
+
data.is_a?(Array) ? data.grep(String) : []
|
|
38
|
+
rescue Errno::ENOENT, JSON::ParserError
|
|
39
|
+
[]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def write(ids)
|
|
43
|
+
FileUtils.mkdir_p(File.dirname(@path), mode: 0o700)
|
|
44
|
+
File.write(@path, JSON.generate(ids), perm: 0o600)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -12,12 +12,12 @@ module Diffbroker
|
|
|
12
12
|
PRINT_OUTPUT = 120
|
|
13
13
|
|
|
14
14
|
Handle = Struct.new(:pid, :cancelled)
|
|
15
|
-
Outcome = Struct.new(:status, :cost_usd, :error_code, :last_output)
|
|
15
|
+
Outcome = Struct.new(:status, :cost_usd, :error_code, :last_output, :no_result)
|
|
16
16
|
|
|
17
17
|
def initialize(config:, report:, worktree: Worktree.new(root: config.worktree_root), neutralizer: Neutralizer.new,
|
|
18
18
|
gh_check: GhCheck.new, launcher: Launcher.new(name: config.terminal), out: $stdout, grace: CANCEL_GRACE,
|
|
19
19
|
progress_interval: PROGRESS_INTERVAL, run_script: RunScript.new(state_dir: config.state_dir),
|
|
20
|
-
reaper: Reaper.new(root: config.worktree_root))
|
|
20
|
+
reaper: Reaper.new(root: config.worktree_root), sessions: SessionLedger.new(state_dir: config.state_dir))
|
|
21
21
|
@config = config
|
|
22
22
|
@report = report
|
|
23
23
|
@worktree = worktree
|
|
@@ -29,6 +29,7 @@ module Diffbroker
|
|
|
29
29
|
@progress_interval = progress_interval
|
|
30
30
|
@run_script = run_script
|
|
31
31
|
@reaper = reaper
|
|
32
|
+
@sessions = sessions
|
|
32
33
|
@handles = {}
|
|
33
34
|
@running = Set.new
|
|
34
35
|
@cancelled = Set.new
|
|
@@ -132,7 +133,7 @@ module Diffbroker
|
|
|
132
133
|
def run_autonomous(task, agent, mcp_config, dir)
|
|
133
134
|
id = task["task_id"]
|
|
134
135
|
@lock.synchronize { @running << id }
|
|
135
|
-
outcome =
|
|
136
|
+
outcome = run_with_resume(task, agent, mcp_config, dir)
|
|
136
137
|
outcome = verify_after_write(task, outcome, agent, mcp_config, dir) if ExplainVerifier.verified?(task) && outcome.status == "done"
|
|
137
138
|
outcome = publish_branch(task, outcome, dir) if outcome.status == "done"
|
|
138
139
|
finished(id, outcome.status, outcome.cost_usd, outcome.error_code, outcome.last_output)
|
|
@@ -143,6 +144,27 @@ module Diffbroker
|
|
|
143
144
|
end
|
|
144
145
|
end
|
|
145
146
|
|
|
147
|
+
# A chat message the server offered to resume (task["resume"]) continues the agent's earlier session, when this machine made it
|
|
148
|
+
# and this agent can: it gets only the new question and keeps what it read. A session that cannot be resumed (deleted, expired)
|
|
149
|
+
# makes the agent exit without a word: the message then runs again from scratch with the full prompt, which carries the chat.
|
|
150
|
+
def run_with_resume(task, agent, mcp_config, dir)
|
|
151
|
+
resume = resume_for(task, agent)
|
|
152
|
+
return run_process(task, agent, mcp_config, dir) unless resume
|
|
153
|
+
|
|
154
|
+
outcome = run_process(task.merge("prompt" => resume["prompt"], "resume_session_id" => resume["session_id"]), agent, mcp_config, dir)
|
|
155
|
+
return outcome unless outcome.status == "failed" && outcome.no_result
|
|
156
|
+
|
|
157
|
+
say(task["task_id"], "Could not resume the earlier session: starting this message from scratch")
|
|
158
|
+
run_process(task, agent, mcp_config, dir)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def resume_for(task, agent)
|
|
162
|
+
resume = task["resume"]
|
|
163
|
+
return unless resume.is_a?(Hash) && agent.resumes? && resume["agent"] == agent.name && resume["prompt"].to_s != ""
|
|
164
|
+
|
|
165
|
+
resume if @sessions.known?(resume["session_id"])
|
|
166
|
+
end
|
|
167
|
+
|
|
146
168
|
# An update_branch task ends with a rebase the agent may not push (see BranchPublisher): the companion does, here.
|
|
147
169
|
def publish_branch(task, outcome, dir)
|
|
148
170
|
return outcome unless publishes_branch?(task)
|
|
@@ -197,7 +219,7 @@ module Diffbroker
|
|
|
197
219
|
|
|
198
220
|
if handle.cancelled then Outcome.new("cancelled", result&.cost_usd, "cancelled", output)
|
|
199
221
|
elsif status.success? && !result&.error then Outcome.new("done", result&.cost_usd, nil, output)
|
|
200
|
-
else Outcome.new("failed", result&.cost_usd, "agent_failed", output)
|
|
222
|
+
else Outcome.new("failed", result&.cost_usd, "agent_failed", output, result.nil?)
|
|
201
223
|
end
|
|
202
224
|
ensure
|
|
203
225
|
writer.close if writer && !writer.closed?
|
|
@@ -221,6 +243,10 @@ module Diffbroker
|
|
|
221
243
|
case (event = agent.parse_stream(line))
|
|
222
244
|
when Agents::Result then result = event
|
|
223
245
|
when Agents::Progress
|
|
246
|
+
if event.session_id
|
|
247
|
+
@sessions.remember(event.session_id)
|
|
248
|
+
progress(id, event.step, nil, nil, event.session_id)
|
|
249
|
+
end
|
|
224
250
|
next if event.step == last_step || monotonic - last_sent < @progress_interval
|
|
225
251
|
|
|
226
252
|
progress(id, event.step)
|
|
@@ -256,8 +282,8 @@ module Diffbroker
|
|
|
256
282
|
end
|
|
257
283
|
end
|
|
258
284
|
|
|
259
|
-
def progress(id, step, agent = nil, phase = nil)
|
|
260
|
-
@report.call("progress", { "task_id" => id, "step" => step, "agent" => agent, "phase" => phase }.compact)
|
|
285
|
+
def progress(id, step, agent = nil, phase = nil, session_id = nil)
|
|
286
|
+
@report.call("progress", { "task_id" => id, "step" => step, "agent" => agent, "phase" => phase, "session_id" => session_id }.compact)
|
|
261
287
|
say(id, "progress: #{step}") unless step == "starting"
|
|
262
288
|
end
|
|
263
289
|
|
data/lib/diffbroker/version.rb
CHANGED
data/lib/diffbroker.rb
CHANGED
|
@@ -32,12 +32,14 @@ module Diffbroker
|
|
|
32
32
|
autoload :LocalRelay, File.expand_path("diffbroker/local_relay", __dir__)
|
|
33
33
|
autoload :Login, File.expand_path("diffbroker/login", __dir__)
|
|
34
34
|
autoload :Neutralizer, File.expand_path("diffbroker/neutralizer", __dir__)
|
|
35
|
+
autoload :PlanUsage, File.expand_path("diffbroker/plan_usage", __dir__)
|
|
35
36
|
autoload :ProcessScanner, File.expand_path("diffbroker/process_scanner", __dir__)
|
|
36
37
|
autoload :Reaper, File.expand_path("diffbroker/reaper", __dir__)
|
|
37
38
|
autoload :RepoScanner, File.expand_path("diffbroker/repo_scanner", __dir__)
|
|
38
39
|
autoload :RunScript, File.expand_path("diffbroker/run_script", __dir__)
|
|
39
40
|
autoload :Runner, File.expand_path("diffbroker/runner", __dir__)
|
|
40
41
|
autoload :ServiceInstaller, File.expand_path("diffbroker/service_installer", __dir__)
|
|
42
|
+
autoload :SessionLedger, File.expand_path("diffbroker/session_ledger", __dir__)
|
|
41
43
|
autoload :SkillInstaller, File.expand_path("diffbroker/skill_installer", __dir__)
|
|
42
44
|
autoload :TaskFiles, File.expand_path("diffbroker/task_files", __dir__)
|
|
43
45
|
autoload :TaskRunner, File.expand_path("diffbroker/task_runner", __dir__)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: diffbroker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yuri Sidorov (@newstler)
|
|
@@ -56,12 +56,14 @@ files:
|
|
|
56
56
|
- lib/diffbroker/login.rb
|
|
57
57
|
- lib/diffbroker/maintenance.rb
|
|
58
58
|
- lib/diffbroker/neutralizer.rb
|
|
59
|
+
- lib/diffbroker/plan_usage.rb
|
|
59
60
|
- lib/diffbroker/process_scanner.rb
|
|
60
61
|
- lib/diffbroker/reaper.rb
|
|
61
62
|
- lib/diffbroker/repo_scanner.rb
|
|
62
63
|
- lib/diffbroker/run_script.rb
|
|
63
64
|
- lib/diffbroker/runner.rb
|
|
64
65
|
- lib/diffbroker/service_installer.rb
|
|
66
|
+
- lib/diffbroker/session_ledger.rb
|
|
65
67
|
- lib/diffbroker/skill_installer.rb
|
|
66
68
|
- lib/diffbroker/task_files.rb
|
|
67
69
|
- lib/diffbroker/task_runner.rb
|