lex-swarm 0.1.2 → 0.2.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/legion/extensions/swarm/actors/workspace_sync.rb +54 -0
- data/lib/legion/extensions/swarm/client.rb +2 -0
- data/lib/legion/extensions/swarm/helpers/workspace.rb +91 -0
- data/lib/legion/extensions/swarm/runners/workspace.rb +62 -0
- data/lib/legion/extensions/swarm/version.rb +1 -1
- data/lib/legion/extensions/swarm.rb +2 -0
- data/spec/legion/extensions/swarm/actors/workspace_sync_spec.rb +48 -0
- data/spec/legion/extensions/swarm/client_spec.rb +20 -0
- data/spec/legion/extensions/swarm/helpers/workspace_spec.rb +108 -0
- data/spec/legion/extensions/swarm/runners/workspace_spec.rb +79 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 578c4f78e11c1cf390624da98867d76b55274db01186715405a0e423645b99ae
|
|
4
|
+
data.tar.gz: 4584ad7f4ebe1c53eef32337404d21f930e23703f182f5f2ea835bb2c6982778
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be53ef935c5ae6c6196ed41ffa15b0cbfc8ce1c708d37fccf2ecc8d75c91a511f6e2132952a7449fa58e9d56d762a8a72876a0cb9c81a0b93ce214399b168f9b
|
|
7
|
+
data.tar.gz: 5707c99712feea161e00e88a70767e74bbdd3daddb9509d99cd4b94d5236a9f246cedf109cba9a64d1077311f540163418f1f81b47f0603583334661285ad4dc
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
require_relative '../helpers/workspace'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Swarm
|
|
9
|
+
module Actors
|
|
10
|
+
class WorkspaceSync
|
|
11
|
+
ROUTING_PREFIX = 'swarm.workspace'
|
|
12
|
+
|
|
13
|
+
def publish_change(charter_id:, key:, operation:, value: nil, author: nil, version: nil, **) # rubocop:disable Metrics/ParameterLists
|
|
14
|
+
return { success: true, skipped: :no_transport } unless defined?(Legion::Transport)
|
|
15
|
+
|
|
16
|
+
routing_key = "#{ROUTING_PREFIX}.#{charter_id}"
|
|
17
|
+
payload = { charter_id: charter_id, key: key, value: value,
|
|
18
|
+
author: author, version: version, operation: operation,
|
|
19
|
+
timestamp: Time.now.utc.to_s }
|
|
20
|
+
|
|
21
|
+
Legion::Transport.publish(routing_key: routing_key, payload: payload)
|
|
22
|
+
Legion::Logging.debug "[swarm-workspace-sync] published #{operation} #{key} to #{routing_key}"
|
|
23
|
+
{ success: true, routing_key: routing_key }
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
Legion::Logging.warn "[swarm-workspace-sync] publish failed: #{e.message}"
|
|
26
|
+
{ success: true, skipped: :publish_error, message: e.message }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def apply_incoming(charter_id:, key:, operation:, value: nil, author: nil, version: nil, timestamp: nil, **) # rubocop:disable Metrics/ParameterLists
|
|
30
|
+
op = operation.to_s
|
|
31
|
+
case op
|
|
32
|
+
when 'put'
|
|
33
|
+
ts = timestamp.is_a?(String) ? Time.parse(timestamp) : (timestamp || Time.now.utc)
|
|
34
|
+
applied = workspace.apply_remote(charter_id, key: key, value: value,
|
|
35
|
+
author: author, version: version || 1, timestamp: ts)
|
|
36
|
+
{ success: true, applied: applied }
|
|
37
|
+
when 'delete'
|
|
38
|
+
workspace.delete(charter_id, key: key)
|
|
39
|
+
{ success: true, applied: true }
|
|
40
|
+
else
|
|
41
|
+
{ success: false, reason: :unknown_operation, operation: op }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def workspace
|
|
48
|
+
@workspace ||= Helpers::Workspace.new
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
require 'legion/extensions/swarm/helpers/charter'
|
|
4
4
|
require 'legion/extensions/swarm/helpers/swarm_store'
|
|
5
5
|
require 'legion/extensions/swarm/runners/swarm'
|
|
6
|
+
require 'legion/extensions/swarm/runners/workspace'
|
|
6
7
|
|
|
7
8
|
module Legion
|
|
8
9
|
module Extensions
|
|
9
10
|
module Swarm
|
|
10
11
|
class Client
|
|
11
12
|
include Runners::Swarm
|
|
13
|
+
include Runners::Workspace
|
|
12
14
|
|
|
13
15
|
def initialize(**)
|
|
14
16
|
@swarm_store = Helpers::SwarmStore.new
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Swarm
|
|
6
|
+
module Helpers
|
|
7
|
+
class Workspace
|
|
8
|
+
def initialize
|
|
9
|
+
@store = {}
|
|
10
|
+
@mutex = Mutex.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def put(charter_id, key:, value:, author:)
|
|
14
|
+
@mutex.synchronize do
|
|
15
|
+
charter_store = (@store[charter_id] ||= {})
|
|
16
|
+
existing = charter_store[key]
|
|
17
|
+
version = existing ? existing[:version] + 1 : 1
|
|
18
|
+
charter_store[key] = {
|
|
19
|
+
key: key,
|
|
20
|
+
value: value,
|
|
21
|
+
author: author,
|
|
22
|
+
version: version,
|
|
23
|
+
timestamp: Time.now.utc
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get(charter_id, key:)
|
|
29
|
+
@mutex.synchronize do
|
|
30
|
+
@store.dig(charter_id, key)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def list(charter_id)
|
|
35
|
+
@mutex.synchronize do
|
|
36
|
+
(@store[charter_id] || {}).dup
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def delete(charter_id, key:)
|
|
41
|
+
@mutex.synchronize do
|
|
42
|
+
charter_store = @store[charter_id]
|
|
43
|
+
return nil unless charter_store
|
|
44
|
+
|
|
45
|
+
charter_store.delete(key)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def clear_charter(charter_id)
|
|
50
|
+
@mutex.synchronize do
|
|
51
|
+
@store.delete(charter_id)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def apply_remote(charter_id, **entry)
|
|
56
|
+
key = entry[:key]
|
|
57
|
+
value = entry[:value]
|
|
58
|
+
author = entry[:author]
|
|
59
|
+
version = entry[:version]
|
|
60
|
+
timestamp = entry[:timestamp]
|
|
61
|
+
|
|
62
|
+
@mutex.synchronize do
|
|
63
|
+
charter_store = (@store[charter_id] ||= {})
|
|
64
|
+
existing = charter_store[key]
|
|
65
|
+
|
|
66
|
+
if existing.nil? || version > existing[:version] ||
|
|
67
|
+
(version == existing[:version] && timestamp > existing[:timestamp])
|
|
68
|
+
charter_store[key] = {
|
|
69
|
+
key: key,
|
|
70
|
+
value: value,
|
|
71
|
+
author: author,
|
|
72
|
+
version: version,
|
|
73
|
+
timestamp: timestamp
|
|
74
|
+
}
|
|
75
|
+
return true
|
|
76
|
+
end
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def stats
|
|
82
|
+
@mutex.synchronize do
|
|
83
|
+
total = @store.values.sum(&:size)
|
|
84
|
+
{ charter_count: @store.size, total_entries: total }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/workspace'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Swarm
|
|
8
|
+
module Runners
|
|
9
|
+
module Workspace
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
12
|
+
|
|
13
|
+
def workspace_put(charter_id:, key:, value:, author:, **)
|
|
14
|
+
entry = workspace.put(charter_id, key: key, value: value, author: author)
|
|
15
|
+
Legion::Logging.debug "[swarm-workspace] put: charter=#{charter_id[0..7]} key=#{key} v=#{entry[:version]}"
|
|
16
|
+
{ success: true, key: key, version: entry[:version], charter_id: charter_id }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def workspace_get(charter_id:, key:, **)
|
|
20
|
+
entry = workspace.get(charter_id, key: key)
|
|
21
|
+
if entry
|
|
22
|
+
{ success: true, entry: entry }
|
|
23
|
+
else
|
|
24
|
+
{ success: false, reason: :not_found, key: key, charter_id: charter_id }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def workspace_list(charter_id:, **)
|
|
29
|
+
entries = workspace.list(charter_id)
|
|
30
|
+
{ success: true, entries: entries, count: entries.size }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def workspace_delete(charter_id:, key:, **)
|
|
34
|
+
removed = workspace.delete(charter_id, key: key)
|
|
35
|
+
if removed
|
|
36
|
+
Legion::Logging.debug "[swarm-workspace] delete: charter=#{charter_id[0..7]} key=#{key}"
|
|
37
|
+
{ success: true, key: key, charter_id: charter_id }
|
|
38
|
+
else
|
|
39
|
+
{ success: false, reason: :not_found, key: key, charter_id: charter_id }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def workspace_clear(charter_id:, **)
|
|
44
|
+
workspace.clear_charter(charter_id)
|
|
45
|
+
Legion::Logging.debug "[swarm-workspace] clear: charter=#{charter_id[0..7]}"
|
|
46
|
+
{ success: true, charter_id: charter_id }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def workspace_stats(**)
|
|
50
|
+
workspace.stats.merge(success: true)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def workspace
|
|
56
|
+
@workspace ||= Helpers::Workspace.new
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -4,8 +4,10 @@ require 'legion/extensions/swarm/version'
|
|
|
4
4
|
require 'legion/extensions/swarm/helpers/charter'
|
|
5
5
|
require 'legion/extensions/swarm/helpers/swarm_store'
|
|
6
6
|
require 'legion/extensions/swarm/helpers/sub_agent'
|
|
7
|
+
require 'legion/extensions/swarm/helpers/workspace'
|
|
7
8
|
require 'legion/extensions/swarm/runners/swarm'
|
|
8
9
|
require 'legion/extensions/swarm/runners/spawn_child'
|
|
10
|
+
require 'legion/extensions/swarm/runners/workspace'
|
|
9
11
|
|
|
10
12
|
module Legion
|
|
11
13
|
module Extensions
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require_relative '../../../../../lib/legion/extensions/swarm/actors/workspace_sync'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Legion::Extensions::Swarm::Actors::WorkspaceSync do
|
|
7
|
+
subject(:actor) { described_class.allocate }
|
|
8
|
+
|
|
9
|
+
describe '#publish_change' do
|
|
10
|
+
it 'returns skipped when transport is not available' do
|
|
11
|
+
result = actor.publish_change(charter_id: 'c1', key: 'k', value: 'v',
|
|
12
|
+
author: 'a', version: 1, operation: :put)
|
|
13
|
+
expect(result[:success]).to be true
|
|
14
|
+
expect(result[:skipped]).to eq(:no_transport)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#apply_incoming' do
|
|
19
|
+
let(:workspace) { Legion::Extensions::Swarm::Helpers::Workspace.new }
|
|
20
|
+
|
|
21
|
+
before { actor.instance_variable_set(:@workspace, workspace) }
|
|
22
|
+
|
|
23
|
+
it 'applies a remote put operation' do
|
|
24
|
+
result = actor.apply_incoming(
|
|
25
|
+
charter_id: 'c1', key: 'shared', value: 'remote-data',
|
|
26
|
+
author: 'remote-agent', version: 1, timestamp: Time.now.utc.to_s, operation: 'put'
|
|
27
|
+
)
|
|
28
|
+
expect(result[:success]).to be true
|
|
29
|
+
expect(result[:applied]).to be true
|
|
30
|
+
expect(workspace.get('c1', key: 'shared')[:value]).to eq('remote-data')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'applies a remote delete operation' do
|
|
34
|
+
workspace.put('c1', key: 'doomed', value: 'bye', author: 'local')
|
|
35
|
+
result = actor.apply_incoming(
|
|
36
|
+
charter_id: 'c1', key: 'doomed', operation: 'delete'
|
|
37
|
+
)
|
|
38
|
+
expect(result[:success]).to be true
|
|
39
|
+
expect(workspace.get('c1', key: 'doomed')).to be_nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'rejects unknown operations' do
|
|
43
|
+
result = actor.apply_incoming(charter_id: 'c1', key: 'x', operation: 'explode')
|
|
44
|
+
expect(result[:success]).to be false
|
|
45
|
+
expect(result[:reason]).to eq(:unknown_operation)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -13,4 +13,24 @@ RSpec.describe Legion::Extensions::Swarm::Client do
|
|
|
13
13
|
expect(client).to respond_to(:active_swarms)
|
|
14
14
|
expect(client).to respond_to(:swarm_status)
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
it 'responds to workspace runner methods' do
|
|
18
|
+
client = described_class.new
|
|
19
|
+
expect(client).to respond_to(:workspace_put)
|
|
20
|
+
expect(client).to respond_to(:workspace_get)
|
|
21
|
+
expect(client).to respond_to(:workspace_list)
|
|
22
|
+
expect(client).to respond_to(:workspace_delete)
|
|
23
|
+
expect(client).to respond_to(:workspace_clear)
|
|
24
|
+
expect(client).to respond_to(:workspace_stats)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#workspace_put and #workspace_get' do
|
|
28
|
+
it 'stores and retrieves workspace entries via client' do
|
|
29
|
+
client = described_class.new
|
|
30
|
+
client.workspace_put(charter_id: 'c1', key: 'data', value: 'hello', author: 'a')
|
|
31
|
+
result = client.workspace_get(charter_id: 'c1', key: 'data')
|
|
32
|
+
expect(result[:success]).to be true
|
|
33
|
+
expect(result[:entry][:value]).to eq('hello')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
16
36
|
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Swarm::Helpers::Workspace do
|
|
6
|
+
subject(:workspace) { described_class.new }
|
|
7
|
+
|
|
8
|
+
let(:charter_id) { 'charter-abc' }
|
|
9
|
+
|
|
10
|
+
describe '#put and #get' do
|
|
11
|
+
it 'stores and retrieves an entry' do
|
|
12
|
+
workspace.put(charter_id, key: 'findings', value: { data: [1, 2, 3] }, author: 'agent-a')
|
|
13
|
+
entry = workspace.get(charter_id, key: 'findings')
|
|
14
|
+
expect(entry[:value]).to eq({ data: [1, 2, 3] })
|
|
15
|
+
expect(entry[:author]).to eq('agent-a')
|
|
16
|
+
expect(entry[:version]).to eq(1)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'increments version on overwrite' do
|
|
20
|
+
workspace.put(charter_id, key: 'result', value: 'v1', author: 'agent-a')
|
|
21
|
+
workspace.put(charter_id, key: 'result', value: 'v2', author: 'agent-b')
|
|
22
|
+
entry = workspace.get(charter_id, key: 'result')
|
|
23
|
+
expect(entry[:value]).to eq('v2')
|
|
24
|
+
expect(entry[:author]).to eq('agent-b')
|
|
25
|
+
expect(entry[:version]).to eq(2)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'returns nil for missing key' do
|
|
29
|
+
expect(workspace.get(charter_id, key: 'nope')).to be_nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'returns nil for missing charter' do
|
|
33
|
+
expect(workspace.get('nonexistent', key: 'x')).to be_nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#list' do
|
|
38
|
+
it 'returns all entries for a charter' do
|
|
39
|
+
workspace.put(charter_id, key: 'a', value: 1, author: 'x')
|
|
40
|
+
workspace.put(charter_id, key: 'b', value: 2, author: 'y')
|
|
41
|
+
entries = workspace.list(charter_id)
|
|
42
|
+
expect(entries.keys).to contain_exactly('a', 'b')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'returns empty hash for unknown charter' do
|
|
46
|
+
expect(workspace.list('unknown')).to eq({})
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '#delete' do
|
|
51
|
+
it 'removes an entry and returns it' do
|
|
52
|
+
workspace.put(charter_id, key: 'temp', value: 'data', author: 'a')
|
|
53
|
+
removed = workspace.delete(charter_id, key: 'temp')
|
|
54
|
+
expect(removed[:value]).to eq('data')
|
|
55
|
+
expect(workspace.get(charter_id, key: 'temp')).to be_nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'returns nil when key does not exist' do
|
|
59
|
+
expect(workspace.delete(charter_id, key: 'nope')).to be_nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe '#clear_charter' do
|
|
64
|
+
it 'removes all entries for a charter' do
|
|
65
|
+
workspace.put(charter_id, key: 'a', value: 1, author: 'x')
|
|
66
|
+
workspace.put(charter_id, key: 'b', value: 2, author: 'x')
|
|
67
|
+
workspace.clear_charter(charter_id)
|
|
68
|
+
expect(workspace.list(charter_id)).to eq({})
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe '#apply_remote' do
|
|
73
|
+
it 'applies a remote put when version is higher' do
|
|
74
|
+
workspace.put(charter_id, key: 'shared', value: 'local', author: 'a')
|
|
75
|
+
workspace.apply_remote(charter_id, key: 'shared', value: 'remote', author: 'b',
|
|
76
|
+
version: 5, timestamp: Time.now.utc)
|
|
77
|
+
expect(workspace.get(charter_id, key: 'shared')[:value]).to eq('remote')
|
|
78
|
+
expect(workspace.get(charter_id, key: 'shared')[:version]).to eq(5)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'ignores remote put when version is lower' do
|
|
82
|
+
workspace.put(charter_id, key: 'shared', value: 'local', author: 'a')
|
|
83
|
+
workspace.put(charter_id, key: 'shared', value: 'local2', author: 'a')
|
|
84
|
+
workspace.apply_remote(charter_id, key: 'shared', value: 'stale', author: 'b',
|
|
85
|
+
version: 1, timestamp: Time.now.utc)
|
|
86
|
+
expect(workspace.get(charter_id, key: 'shared')[:value]).to eq('local2')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'uses timestamp to break version ties' do
|
|
90
|
+
workspace.put(charter_id, key: 'tie', value: 'old', author: 'a')
|
|
91
|
+
later = Time.now.utc + 1
|
|
92
|
+
workspace.apply_remote(charter_id, key: 'tie', value: 'newer', author: 'b',
|
|
93
|
+
version: 1, timestamp: later)
|
|
94
|
+
expect(workspace.get(charter_id, key: 'tie')[:value]).to eq('newer')
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe '#stats' do
|
|
99
|
+
it 'returns charter count and total entries' do
|
|
100
|
+
workspace.put('c1', key: 'a', value: 1, author: 'x')
|
|
101
|
+
workspace.put('c1', key: 'b', value: 2, author: 'x')
|
|
102
|
+
workspace.put('c2', key: 'c', value: 3, author: 'y')
|
|
103
|
+
stats = workspace.stats
|
|
104
|
+
expect(stats[:charter_count]).to eq(2)
|
|
105
|
+
expect(stats[:total_entries]).to eq(3)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Swarm::Runners::Workspace do
|
|
6
|
+
subject(:runner) { Object.new.extend(described_class) }
|
|
7
|
+
|
|
8
|
+
let(:charter_id) { 'charter-123' }
|
|
9
|
+
|
|
10
|
+
before { runner.instance_variable_set(:@workspace, nil) }
|
|
11
|
+
|
|
12
|
+
describe '#workspace_put' do
|
|
13
|
+
it 'stores a value and returns success' do
|
|
14
|
+
result = runner.workspace_put(charter_id: charter_id, key: 'notes', value: 'hello', author: 'agent-a')
|
|
15
|
+
expect(result[:success]).to be true
|
|
16
|
+
expect(result[:key]).to eq('notes')
|
|
17
|
+
expect(result[:version]).to eq(1)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe '#workspace_get' do
|
|
22
|
+
it 'retrieves a stored value' do
|
|
23
|
+
runner.workspace_put(charter_id: charter_id, key: 'data', value: [1, 2], author: 'a')
|
|
24
|
+
result = runner.workspace_get(charter_id: charter_id, key: 'data')
|
|
25
|
+
expect(result[:success]).to be true
|
|
26
|
+
expect(result[:entry][:value]).to eq([1, 2])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'returns not_found for missing key' do
|
|
30
|
+
result = runner.workspace_get(charter_id: charter_id, key: 'nope')
|
|
31
|
+
expect(result[:success]).to be false
|
|
32
|
+
expect(result[:reason]).to eq(:not_found)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe '#workspace_list' do
|
|
37
|
+
it 'lists all entries for a charter' do
|
|
38
|
+
runner.workspace_put(charter_id: charter_id, key: 'a', value: 1, author: 'x')
|
|
39
|
+
runner.workspace_put(charter_id: charter_id, key: 'b', value: 2, author: 'x')
|
|
40
|
+
result = runner.workspace_list(charter_id: charter_id)
|
|
41
|
+
expect(result[:success]).to be true
|
|
42
|
+
expect(result[:count]).to eq(2)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe '#workspace_delete' do
|
|
47
|
+
it 'deletes an entry and returns success' do
|
|
48
|
+
runner.workspace_put(charter_id: charter_id, key: 'temp', value: 'x', author: 'a')
|
|
49
|
+
result = runner.workspace_delete(charter_id: charter_id, key: 'temp')
|
|
50
|
+
expect(result[:success]).to be true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'returns not_found for missing key' do
|
|
54
|
+
result = runner.workspace_delete(charter_id: charter_id, key: 'nope')
|
|
55
|
+
expect(result[:success]).to be false
|
|
56
|
+
expect(result[:reason]).to eq(:not_found)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe '#workspace_clear' do
|
|
61
|
+
it 'clears all entries for a charter' do
|
|
62
|
+
runner.workspace_put(charter_id: charter_id, key: 'a', value: 1, author: 'x')
|
|
63
|
+
result = runner.workspace_clear(charter_id: charter_id)
|
|
64
|
+
expect(result[:success]).to be true
|
|
65
|
+
expect(runner.workspace_list(charter_id: charter_id)[:count]).to eq(0)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe '#workspace_stats' do
|
|
70
|
+
it 'returns workspace statistics' do
|
|
71
|
+
runner.workspace_put(charter_id: 'c1', key: 'a', value: 1, author: 'x')
|
|
72
|
+
runner.workspace_put(charter_id: 'c2', key: 'b', value: 2, author: 'y')
|
|
73
|
+
result = runner.workspace_stats
|
|
74
|
+
expect(result[:success]).to be true
|
|
75
|
+
expect(result[:charter_count]).to eq(2)
|
|
76
|
+
expect(result[:total_entries]).to eq(2)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-swarm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -35,21 +35,27 @@ files:
|
|
|
35
35
|
- lib/legion/extensions/swarm.rb
|
|
36
36
|
- lib/legion/extensions/swarm/actors/orphan_sweeper.rb
|
|
37
37
|
- lib/legion/extensions/swarm/actors/stale_check.rb
|
|
38
|
+
- lib/legion/extensions/swarm/actors/workspace_sync.rb
|
|
38
39
|
- lib/legion/extensions/swarm/client.rb
|
|
39
40
|
- lib/legion/extensions/swarm/helpers/charter.rb
|
|
40
41
|
- lib/legion/extensions/swarm/helpers/sub_agent.rb
|
|
41
42
|
- lib/legion/extensions/swarm/helpers/swarm_store.rb
|
|
43
|
+
- lib/legion/extensions/swarm/helpers/workspace.rb
|
|
42
44
|
- lib/legion/extensions/swarm/runners/spawn_child.rb
|
|
43
45
|
- lib/legion/extensions/swarm/runners/swarm.rb
|
|
46
|
+
- lib/legion/extensions/swarm/runners/workspace.rb
|
|
44
47
|
- lib/legion/extensions/swarm/version.rb
|
|
45
48
|
- spec/legion/extensions/swarm/actors/orphan_sweeper_spec.rb
|
|
46
49
|
- spec/legion/extensions/swarm/actors/stale_check_spec.rb
|
|
50
|
+
- spec/legion/extensions/swarm/actors/workspace_sync_spec.rb
|
|
47
51
|
- spec/legion/extensions/swarm/client_spec.rb
|
|
48
52
|
- spec/legion/extensions/swarm/helpers/charter_spec.rb
|
|
49
53
|
- spec/legion/extensions/swarm/helpers/sub_agent_spec.rb
|
|
50
54
|
- spec/legion/extensions/swarm/helpers/swarm_store_spec.rb
|
|
55
|
+
- spec/legion/extensions/swarm/helpers/workspace_spec.rb
|
|
51
56
|
- spec/legion/extensions/swarm/runners/spawn_child_spec.rb
|
|
52
57
|
- spec/legion/extensions/swarm/runners/swarm_spec.rb
|
|
58
|
+
- spec/legion/extensions/swarm/runners/workspace_spec.rb
|
|
53
59
|
- spec/spec_helper.rb
|
|
54
60
|
homepage: https://github.com/LegionIO/lex-swarm
|
|
55
61
|
licenses:
|