agentilda 2.0.3 → 2.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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/agentilda/agents/agent.rb +79 -0
  3. data/lib/agentilda/agents/registry.rb +121 -0
  4. data/lib/agentilda/agents/roster.rb +92 -0
  5. data/lib/agentilda/agents.rb +11 -113
  6. data/lib/agentilda/cli/agents/subcommands/describe.rb +3 -3
  7. data/lib/agentilda/cli/agents/subcommands/list.rb +1 -1
  8. data/lib/agentilda/cli/base.rb +3 -3
  9. data/lib/agentilda/cli/create/create.rb +17 -18
  10. data/lib/agentilda/cli/docs/docs.rb +1 -1
  11. data/lib/agentilda/cli/index/index.rb +1 -1
  12. data/lib/agentilda/cli/linear/subcommands/import.rb +1 -1
  13. data/lib/agentilda/cli/list_plans/list_plans.rb +1 -1
  14. data/lib/agentilda/cli/mail/mail.rb +4 -4
  15. data/lib/agentilda/cli/mail/subcommands/send.rb +1 -1
  16. data/lib/agentilda/cli/resync/subcommands/dirs.rb +2 -2
  17. data/lib/agentilda/cli/resync/subcommands/prs.rb +3 -3
  18. data/lib/agentilda/cli/run/run.rb +40 -40
  19. data/lib/agentilda/cli/states/states.rb +1 -1
  20. data/lib/agentilda/cli/unblock/unblock.rb +17 -17
  21. data/lib/agentilda/cli/worktree/worktree.rb +4 -4
  22. data/lib/agentilda/cli.rb +1 -1
  23. data/lib/agentilda/engine/board.rb +93 -0
  24. data/lib/agentilda/engine/dispatcher.rb +646 -0
  25. data/lib/agentilda/engine/progress_log.rb +125 -0
  26. data/lib/agentilda/engine/runner.rb +141 -0
  27. data/lib/agentilda/engine/state_file.rb +176 -0
  28. data/lib/agentilda/engine/tally.rb +177 -0
  29. data/lib/agentilda/engine.rb +19 -0
  30. data/lib/agentilda/execution/child.rb +94 -0
  31. data/lib/agentilda/execution/clock.rb +116 -0
  32. data/lib/agentilda/execution/control.rb +180 -0
  33. data/lib/agentilda/execution/executor.rb +699 -0
  34. data/lib/agentilda/execution/transcript.rb +468 -0
  35. data/lib/agentilda/execution.rb +18 -0
  36. data/lib/agentilda/lifecycle/adoption.rb +197 -0
  37. data/lib/agentilda/lifecycle/brief.rb +285 -0
  38. data/lib/agentilda/lifecycle/creator.rb +125 -0
  39. data/lib/agentilda/lifecycle/resync.rb +428 -0
  40. data/lib/agentilda/lifecycle/unblocker.rb +223 -0
  41. data/lib/agentilda/lifecycle.rb +28 -0
  42. data/lib/agentilda/linear/api.rb +1 -1
  43. data/lib/agentilda/linear/attribution.rb +7 -7
  44. data/lib/agentilda/linear/import.rb +18 -18
  45. data/lib/agentilda/linear/mapping.rb +6 -6
  46. data/lib/agentilda/linear/push.rb +5 -5
  47. data/lib/agentilda/linear/survey.rb +7 -7
  48. data/lib/agentilda/linear/unit.rb +5 -5
  49. data/lib/agentilda/plans/dev_work.rb +56 -0
  50. data/lib/agentilda/plans/feature.rb +131 -0
  51. data/lib/agentilda/plans/index.rb +208 -0
  52. data/lib/agentilda/plans/ledger.rb +235 -0
  53. data/lib/agentilda/plans/mailbox.rb +143 -0
  54. data/lib/agentilda/plans/ordinal.rb +92 -0
  55. data/lib/agentilda/plans/pull_request.rb +29 -0
  56. data/lib/agentilda/plans/pull_requests.rb +229 -0
  57. data/lib/agentilda/plans/state_machine.rb +383 -0
  58. data/lib/agentilda/plans/status.rb +365 -0
  59. data/lib/agentilda/plans/subject.rb +131 -0
  60. data/lib/agentilda/plans/tree.rb +79 -0
  61. data/lib/agentilda/plans.rb +39 -0
  62. data/lib/agentilda/presentation/console.rb +159 -0
  63. data/lib/agentilda/presentation/dashboard.rb +221 -0
  64. data/lib/agentilda/presentation/diagram.rb +149 -0
  65. data/lib/agentilda/presentation/documentation.rb +439 -0
  66. data/lib/agentilda/presentation/keyboard.rb +158 -0
  67. data/lib/agentilda/presentation/reporter.rb +181 -0
  68. data/lib/agentilda/presentation/screen/ratatui/bar.rb +59 -0
  69. data/lib/agentilda/presentation/screen/ratatui/key_translator.rb +31 -0
  70. data/lib/agentilda/presentation/screen/ratatui.rb +320 -0
  71. data/lib/agentilda/presentation/viewer.rb +62 -0
  72. data/lib/agentilda/presentation.rb +23 -0
  73. data/lib/agentilda/{ui.rb → support/ui.rb} +5 -5
  74. data/lib/agentilda/vcs/github.rb +170 -0
  75. data/lib/agentilda/vcs/publisher.rb +177 -0
  76. data/lib/agentilda/vcs/worktree.rb +209 -0
  77. data/lib/agentilda/vcs.rb +19 -0
  78. data/lib/agentilda/version.rb +1 -1
  79. data/lib/agentilda.rb +6 -5
  80. metadata +56 -49
  81. data/lib/agentilda/adoption.rb +0 -197
  82. data/lib/agentilda/agent.rb +0 -77
  83. data/lib/agentilda/board.rb +0 -91
  84. data/lib/agentilda/brief.rb +0 -283
  85. data/lib/agentilda/child.rb +0 -92
  86. data/lib/agentilda/clock.rb +0 -114
  87. data/lib/agentilda/console.rb +0 -157
  88. data/lib/agentilda/control.rb +0 -178
  89. data/lib/agentilda/creator.rb +0 -125
  90. data/lib/agentilda/dashboard.rb +0 -219
  91. data/lib/agentilda/dev_work.rb +0 -56
  92. data/lib/agentilda/diagram.rb +0 -149
  93. data/lib/agentilda/dispatcher.rb +0 -646
  94. data/lib/agentilda/documentation.rb +0 -437
  95. data/lib/agentilda/executor.rb +0 -697
  96. data/lib/agentilda/feature.rb +0 -132
  97. data/lib/agentilda/github.rb +0 -168
  98. data/lib/agentilda/index.rb +0 -206
  99. data/lib/agentilda/keyboard.rb +0 -156
  100. data/lib/agentilda/ledger.rb +0 -233
  101. data/lib/agentilda/mailbox.rb +0 -141
  102. data/lib/agentilda/ordinal.rb +0 -90
  103. data/lib/agentilda/progress_log.rb +0 -126
  104. data/lib/agentilda/publisher.rb +0 -175
  105. data/lib/agentilda/pull_request.rb +0 -27
  106. data/lib/agentilda/pull_requests.rb +0 -230
  107. data/lib/agentilda/reporter.rb +0 -181
  108. data/lib/agentilda/resync.rb +0 -426
  109. data/lib/agentilda/roster.rb +0 -93
  110. data/lib/agentilda/runner.rb +0 -139
  111. data/lib/agentilda/screen/ratatui/bar.rb +0 -57
  112. data/lib/agentilda/screen/ratatui/key_translator.rb +0 -29
  113. data/lib/agentilda/screen/ratatui.rb +0 -318
  114. data/lib/agentilda/state_file.rb +0 -174
  115. data/lib/agentilda/state_machine.rb +0 -383
  116. data/lib/agentilda/status.rb +0 -363
  117. data/lib/agentilda/subject.rb +0 -133
  118. data/lib/agentilda/tally.rb +0 -175
  119. data/lib/agentilda/transcript.rb +0 -466
  120. data/lib/agentilda/tree.rb +0 -81
  121. data/lib/agentilda/unblocker.rb +0 -221
  122. data/lib/agentilda/viewer.rb +0 -60
  123. data/lib/agentilda/worktree.rb +0 -207
  124. /data/lib/agentilda/{config.rb → support/config.rb} +0 -0
  125. /data/lib/agentilda/{frontmatter.rb → support/frontmatter.rb} +0 -0
  126. /data/lib/agentilda/{markdown.rb → support/markdown.rb} +0 -0
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agentilda
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
@@ -294,12 +294,10 @@ files:
294
294
  - exe/tilda
295
295
  - exe/tilda-rs
296
296
  - lib/agentilda.rb
297
- - lib/agentilda/adoption.rb
298
- - lib/agentilda/agent.rb
299
297
  - lib/agentilda/agents.rb
300
- - lib/agentilda/board.rb
301
- - lib/agentilda/brief.rb
302
- - lib/agentilda/child.rb
298
+ - lib/agentilda/agents/agent.rb
299
+ - lib/agentilda/agents/registry.rb
300
+ - lib/agentilda/agents/roster.rb
303
301
  - lib/agentilda/cli.rb
304
302
  - lib/agentilda/cli/agents/subcommands/describe.rb
305
303
  - lib/agentilda/cli/agents/subcommands/list.rb
@@ -321,24 +319,26 @@ files:
321
319
  - lib/agentilda/cli/unblock/unblock.rb
322
320
  - lib/agentilda/cli/version/version.rb
323
321
  - lib/agentilda/cli/worktree/worktree.rb
324
- - lib/agentilda/clock.rb
325
- - lib/agentilda/config.rb
326
- - lib/agentilda/console.rb
327
- - lib/agentilda/control.rb
328
- - lib/agentilda/creator.rb
329
- - lib/agentilda/dashboard.rb
330
- - lib/agentilda/dev_work.rb
331
- - lib/agentilda/diagram.rb
332
- - lib/agentilda/dispatcher.rb
333
- - lib/agentilda/documentation.rb
334
- - lib/agentilda/executor.rb
335
- - lib/agentilda/feature.rb
336
- - lib/agentilda/frontmatter.rb
337
- - lib/agentilda/github.rb
338
- - lib/agentilda/index.rb
339
- - lib/agentilda/keyboard.rb
322
+ - lib/agentilda/engine.rb
323
+ - lib/agentilda/engine/board.rb
324
+ - lib/agentilda/engine/dispatcher.rb
325
+ - lib/agentilda/engine/progress_log.rb
326
+ - lib/agentilda/engine/runner.rb
327
+ - lib/agentilda/engine/state_file.rb
328
+ - lib/agentilda/engine/tally.rb
329
+ - lib/agentilda/execution.rb
330
+ - lib/agentilda/execution/child.rb
331
+ - lib/agentilda/execution/clock.rb
332
+ - lib/agentilda/execution/control.rb
333
+ - lib/agentilda/execution/executor.rb
334
+ - lib/agentilda/execution/transcript.rb
340
335
  - lib/agentilda/launcher.rb
341
- - lib/agentilda/ledger.rb
336
+ - lib/agentilda/lifecycle.rb
337
+ - lib/agentilda/lifecycle/adoption.rb
338
+ - lib/agentilda/lifecycle/brief.rb
339
+ - lib/agentilda/lifecycle/creator.rb
340
+ - lib/agentilda/lifecycle/resync.rb
341
+ - lib/agentilda/lifecycle/unblocker.rb
342
342
  - lib/agentilda/linear.rb
343
343
  - lib/agentilda/linear/api.rb
344
344
  - lib/agentilda/linear/attribution.rb
@@ -349,32 +349,39 @@ files:
349
349
  - lib/agentilda/linear/push.rb
350
350
  - lib/agentilda/linear/survey.rb
351
351
  - lib/agentilda/linear/unit.rb
352
- - lib/agentilda/mailbox.rb
353
- - lib/agentilda/markdown.rb
354
- - lib/agentilda/ordinal.rb
355
- - lib/agentilda/progress_log.rb
356
- - lib/agentilda/publisher.rb
357
- - lib/agentilda/pull_request.rb
358
- - lib/agentilda/pull_requests.rb
359
- - lib/agentilda/reporter.rb
360
- - lib/agentilda/resync.rb
361
- - lib/agentilda/roster.rb
362
- - lib/agentilda/runner.rb
363
- - lib/agentilda/screen/ratatui.rb
364
- - lib/agentilda/screen/ratatui/bar.rb
365
- - lib/agentilda/screen/ratatui/key_translator.rb
366
- - lib/agentilda/state_file.rb
367
- - lib/agentilda/state_machine.rb
368
- - lib/agentilda/status.rb
369
- - lib/agentilda/subject.rb
370
- - lib/agentilda/tally.rb
371
- - lib/agentilda/transcript.rb
372
- - lib/agentilda/tree.rb
373
- - lib/agentilda/ui.rb
374
- - lib/agentilda/unblocker.rb
352
+ - lib/agentilda/plans.rb
353
+ - lib/agentilda/plans/dev_work.rb
354
+ - lib/agentilda/plans/feature.rb
355
+ - lib/agentilda/plans/index.rb
356
+ - lib/agentilda/plans/ledger.rb
357
+ - lib/agentilda/plans/mailbox.rb
358
+ - lib/agentilda/plans/ordinal.rb
359
+ - lib/agentilda/plans/pull_request.rb
360
+ - lib/agentilda/plans/pull_requests.rb
361
+ - lib/agentilda/plans/state_machine.rb
362
+ - lib/agentilda/plans/status.rb
363
+ - lib/agentilda/plans/subject.rb
364
+ - lib/agentilda/plans/tree.rb
365
+ - lib/agentilda/presentation.rb
366
+ - lib/agentilda/presentation/console.rb
367
+ - lib/agentilda/presentation/dashboard.rb
368
+ - lib/agentilda/presentation/diagram.rb
369
+ - lib/agentilda/presentation/documentation.rb
370
+ - lib/agentilda/presentation/keyboard.rb
371
+ - lib/agentilda/presentation/reporter.rb
372
+ - lib/agentilda/presentation/screen/ratatui.rb
373
+ - lib/agentilda/presentation/screen/ratatui/bar.rb
374
+ - lib/agentilda/presentation/screen/ratatui/key_translator.rb
375
+ - lib/agentilda/presentation/viewer.rb
376
+ - lib/agentilda/support/config.rb
377
+ - lib/agentilda/support/frontmatter.rb
378
+ - lib/agentilda/support/markdown.rb
379
+ - lib/agentilda/support/ui.rb
380
+ - lib/agentilda/vcs.rb
381
+ - lib/agentilda/vcs/github.rb
382
+ - lib/agentilda/vcs/publisher.rb
383
+ - lib/agentilda/vcs/worktree.rb
375
384
  - lib/agentilda/version.rb
376
- - lib/agentilda/viewer.rb
377
- - lib/agentilda/worktree.rb
378
385
  homepage: https://github.com/kigster/agentilda
379
386
  licenses: []
380
387
  metadata:
@@ -395,7 +402,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
395
402
  - !ruby/object:Gem::Version
396
403
  version: '0'
397
404
  requirements: []
398
- rubygems_version: 4.0.21
405
+ rubygems_version: 4.0.22
399
406
  specification_version: 4
400
407
  summary: 'Agentic specification-driven development: spec, plan, build, review'
401
408
  test_files: []
@@ -1,197 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fileutils"
4
- require "parallel"
5
-
6
- require "agentilda/status"
7
-
8
- module Agentilda
9
- # Gives a plan folder to every pull request that has no plan to point at.
10
- #
11
- # `resync prs` can resolve most pull requests from their branch or their
12
- # diff. What it cannot resolve it used to flag for a human — "diff touches
13
- # 021.00, 022.00, 024.00 and none is obviously primary" — and stop. That is
14
- # honest but not useful: the work exists, it is unspecified, and leaving it
15
- # unnumbered means it never appears in the index at all.
16
- #
17
- # So an orphan is adopted instead. It gets a retroactive plan of its own,
18
- # numbered in the gap after the furthest plan it can see, holding the pull
19
- # request that produced it.
20
- #
21
- # == Why the numbering is allocated serially
22
- #
23
- # The obvious parallel design — every worker reads the highest number and
24
- # adds `.01` — is deterministic but not unique, and the collision is the
25
- # common case rather than the rare one. Fifteen open pull requests branched
26
- # off the same main all see the same highest number and all compute the same
27
- # slot. Determinism is not the property that matters here; distinctness is.
28
- #
29
- # So the pipeline is **gather in parallel, allocate serially, apply in
30
- # parallel**. The allocation is pure arithmetic over an array that is
31
- # already in memory, so the serial section costs microseconds; the two
32
- # expensive phases — reading each branch and writing each folder — keep the
33
- # whole fan-out. And because the slots are handed out before any worker
34
- # starts, no worker can want a number another worker holds: no locks, no
35
- # retries, and the same input produces the same numbers every run.
36
- class Adoption
37
- # One pull request and the plan it is being given.
38
- #
39
- # @!attribute [r] pull
40
- # @return [Hash] as returned by {GitHub#pulls}
41
- # @!attribute [r] ordinal
42
- # @return [Agentilda::Ordinal] the slot it was allocated
43
- # @!attribute [r] major
44
- # @return [Integer] the furthest plan its branch or diff could see
45
- # @!attribute [r] path
46
- # @return [String, nil] the folder, once created
47
- Adoptee = Data.define(:pull, :ordinal, :major, :path) do
48
- # @return [String] the folder name this pull request earns
49
- def dirname = Agentilda.plan_dirname(ordinal, STATUS_BY_KEY.fetch(:retroactive), slug)
50
-
51
- # The PR title, stripped of any prefix, as the folder's tail.
52
- #
53
- # @return [String]
54
- def slug = Creator.slugify(pull[:title].to_s.sub(/\A\[[^\]]+\](?:\([A-Z]\))?\s*/, ""))
55
-
56
- # @return [String] a single auditable line
57
- def to_s = "##{pull[:number]} → #{dirname}"
58
- end
59
-
60
- # @param tree [Agentilda::Tree]
61
- # @param github [Agentilda::GitHub]
62
- # @param root [String] repository root, for reading branches
63
- # @param jobs [Integer] workers for the two parallel phases
64
- def initialize(tree:, github: GitHub.new, root: nil, jobs: UI.default_jobs)
65
- @tree = tree
66
- @github = github
67
- @root = root || File.dirname(tree.dir)
68
- @jobs = jobs
69
- end
70
-
71
- # @return [Agentilda::Tree]
72
- attr_reader :tree
73
-
74
- # @return [Agentilda::GitHub]
75
- attr_reader :github
76
-
77
- # @return [String]
78
- attr_reader :root
79
-
80
- # @return [Integer]
81
- attr_reader :jobs
82
-
83
- # Work out what each orphan would be given, without creating anything.
84
- #
85
- # @param pulls [Array<Hash>] the orphans, from {Resync::Prs}
86
- # @return [Array<Agentilda::Adoption::Adoptee>] in pull request order
87
- def plan(pulls)
88
- allocate(gather(pulls))
89
- end
90
-
91
- # Adopt every orphan: create its folder and record its pull request.
92
- #
93
- # @param pulls [Array<Hash>]
94
- # @return [Array<Agentilda::Adoption::Adoptee>] with `path` filled in
95
- def call(pulls)
96
- adoptees = plan(pulls)
97
- return adoptees if adoptees.empty?
98
-
99
- created = Parallel.map(adoptees, in_threads: jobs) { |adoptee| adopt(adoptee) }
100
- tree.reload
101
- created
102
- end
103
-
104
- private
105
-
106
- # Phase one, in parallel: ask each pull request's branch how far the
107
- # sequence had got when the work started. Branches are not all rebased
108
- # onto the same main, so this is per-branch rather than tree-wide, and it
109
- # is a network-free `ls-tree` per pull request.
110
- #
111
- # @param pulls [Array<Hash>]
112
- # @return [Array<Array(Hash, Integer)>] each pull with its major
113
- def gather(pulls)
114
- ordered = pulls.sort_by { |pull| pull[:number].to_i }
115
-
116
- Parallel.map(ordered, in_threads: jobs) { |pull| [pull, major_for(pull)] }
117
- end
118
-
119
- # The furthest whole plan this pull request can see: on its own branch
120
- # first, then whatever it touched, then the tree we are standing in. A
121
- # pull request whose branch is long gone still has a diff.
122
- #
123
- # @param pull [Hash]
124
- # @return [Integer]
125
- def major_for(pull)
126
- candidates = Agentilda.plans_on_ref(root, pull[:branch].to_s)
127
- candidates = touched(pull) if candidates.empty?
128
- candidates = tree.subjects.map { |s| s.feature.ordinal } if candidates.empty?
129
-
130
- candidates.map(&:major).max.to_i
131
- end
132
-
133
- # @param pull [Hash]
134
- # @return [Array<Agentilda::Ordinal>]
135
- def touched(pull)
136
- Array(pull[:files]).filter_map { |path|
137
- parts = path.to_s.split("/")
138
- index = parts.index(Agentilda::PLANS_DIR)
139
- index && parts[index + 1] && Ordinal.from_dirname(parts[index + 1])
140
- }.uniq
141
- end
142
-
143
- # Phase two, serial and deliberately so. Minors are handed out per major,
144
- # continuing past every slot the tree already holds, in ascending pull
145
- # request order — so the numbers follow the order the work was opened in,
146
- # and two pull requests cannot be given the same one.
147
- #
148
- # @param gathered [Array<Array(Hash, Integer)>]
149
- # @return [Array<Agentilda::Adoption::Adoptee>]
150
- def allocate(gathered)
151
- taken = Hash.new { |h, major| h[major] = minors_taken(major) }
152
-
153
- gathered.filter_map do |pull, major|
154
- minor = ((taken[major].max || 0) + 1)
155
- next if minor > Ordinal::MAX_MINOR
156
-
157
- taken[major] << minor
158
- Adoptee.new(pull:, major:, ordinal: Ordinal.new(major:, minor:), path: nil)
159
- end
160
- end
161
-
162
- # @param major [Integer]
163
- # @return [Array<Integer>] minors already used under this major
164
- def minors_taken(major)
165
- tree.subjects.map { |s| s.feature.ordinal }.select { |o| o.major == major }.map(&:minor)
166
- end
167
-
168
- # Phase three, in parallel: each worker owns a number nobody else can
169
- # want, so it can create its folder without coordinating with anyone.
170
- #
171
- # @param adoptee [Agentilda::Adoption::Adoptee]
172
- # @return [Agentilda::Adoption::Adoptee]
173
- def adopt(adoptee)
174
- path = File.join(tree.dir, adoptee.dirname)
175
- return adoptee if File.exist?(path)
176
-
177
- FileUtils.mkdir_p(path)
178
- File.write(File.join(path, PullRequests::FILENAME),
179
- PullRequests.render([pull_request_for(adoptee)]))
180
-
181
- adoptee.with(path:)
182
- end
183
-
184
- # The body is what a specification would be written from, so it is worth
185
- # a second request. A failure here costs the description, not the folder.
186
- #
187
- # @param adoptee [Agentilda::Adoption::Adoptee]
188
- # @return [Hash]
189
- def pull_request_for(adoptee)
190
- pull = adoptee.pull
191
- github.pull_request(pull[:number].to_s)
192
- rescue Agentilda::Error
193
- { number: pull[:number], title: pull[:title], url: pull[:url],
194
- state: pull[:state] || "Unknown", body: "" }
195
- end
196
- end
197
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Agentilda
4
- # One specialist, loaded from `~/.agents/agents/<name>.md`.
5
- #
6
- # The definition files are the single source of truth for who does what: the
7
- # frontmatter says which plan states this agent handles and what it may write,
8
- # and the body is the prompt. Nothing about a specialty is duplicated in Ruby.
9
- #
10
- # @!attribute [r] name
11
- # @return [String]
12
- # @!attribute [r] description
13
- # @return [String]
14
- # @!attribute [r] handles
15
- # @return [Array<Symbol>] plan states this agent is offered work from
16
- # @!attribute [r] advances_to
17
- # @return [Symbol, nil] the state it is expected to reach; nil = read-only
18
- # @!attribute [r] model
19
- # @return [String, nil]
20
- # @!attribute [r] allowed_tools
21
- # @return [Array<String>]
22
- # @!attribute [r] may
23
- # @return [Array<String>] commands lifted from {Executor::FORBIDDEN_COMMANDS}
24
- # for this agent alone. Nothing in {Executor::UNGRANTABLE} can be lifted.
25
- # @!attribute [r] timeout
26
- # @return [Integer, nil] seconds before this agent is abandoned; nil
27
- # defers to the executor's run-wide default
28
- # @!attribute [r] ledger
29
- # @return [Array<String>] the documents this agent signs, in the order
30
- # it reaches them; the last entry it wrote is where it stands
31
- # @!attribute [r] rounds
32
- # @return [Integer] how many times it may be invoked on one plan in one
33
- # state before the plan parks; a Completed never earns another
34
- # @!attribute [r] effort
35
- # @return [String, nil] passed to `claude --effort`
36
- # @!attribute [r] starts_as
37
- # @return [Symbol, nil] the state the harness renames the plan into the
38
- # moment this agent is dispatched, where the topology permits it
39
- # @!attribute [r] holds_at
40
- # @return [Symbol, nil] the state the plan takes when this agent completes
41
- # while its partner on the same plan is still running
42
- Agent = Data.define(:name,
43
- :description,
44
- :handles,
45
- :advances_to,
46
- :model,
47
- :allowed_tools,
48
- :may,
49
- :network,
50
- :timeout,
51
- :prompt,
52
- :path,
53
- :ledger,
54
- :rounds,
55
- :effort,
56
- :starts_as,
57
- :holds_at) do
58
- def initialize(ledger: [], rounds: 1, effort: nil, starts_as: nil, holds_at: nil, **rest) = super
59
-
60
- # @return [Boolean] whether this agent changes anything on disk
61
- def read_only? = advances_to.nil?
62
-
63
- # @param status [Agentilda::Status]
64
- # @return [Boolean]
65
- def handles?(status) = handles.include?(status.key)
66
-
67
- # The word after the hyphen: `researcher`, `backend`. What the screen's
68
- # agent column shows, the name being too long for it.
69
- #
70
- # @return [String]
71
- def role = name.split("-", 2).last.to_s
72
- end
73
-
74
- # More rounds than this costs tokens and buys nothing: an agent that has
75
- # not finished in five tries is not going to on the sixth.
76
- Agent::MAX_ROUNDS = 5
77
- end
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Agentilda
4
- # What the screen draws: everything about the run, as values, refreshed
5
- # once a second by the dispatcher. The screen paints it and knows nothing
6
- # about threads; the dispatcher builds it and knows nothing about cells.
7
- #
8
- # @!attribute [r] rows
9
- # @return [Array<Agentilda::Board::Row>] one per agent invocation still
10
- # worth showing: running, or finished within the last few seconds
11
- Board = Data.define(:started_at,
12
- :status,
13
- :plans,
14
- :up,
15
- :down,
16
- :rows,
17
- :root,
18
- :running,
19
- :live_up,
20
- :live_down,
21
- :selected,
22
- :dialog,
23
- :help,
24
- :about,
25
- :hidden) do
26
- def initialize(selected: nil, dialog: nil, help: false, about: nil, hidden: 0, **rest) = super
27
- end
28
-
29
- # @!attribute [r] key
30
- # @return [String] `"001.00/leah-researcher"`, what selection and the
31
- # keyboard address a row by
32
- # @!attribute [r] file
33
- # @return [String] the document holding the agent's latest ledger line
34
- # @!attribute [r] pr
35
- # @return [Hash, nil] `{number:, url:, rejected:}` once the plan is at
36
- # the review stage
37
- # @!attribute [r] state
38
- # @return [Symbol] :running, :done, :failed or :stuck
39
- # @!attribute [r] phase
40
- # @return [Symbol, nil] the clock's phase while running
41
- # @!attribute [r] frame
42
- # @return [Integer] spinner frame counter
43
- # @!attribute [r] history
44
- # @return [Array<String>] what the agent has said, newest first, for
45
- # `run --scroll-height` to show more than the latest line of
46
- Board::Row = Data.define(:key,
47
- :at,
48
- :ordinal,
49
- :file,
50
- :agent,
51
- :role,
52
- :round,
53
- :rounds,
54
- :model,
55
- :remaining,
56
- :phase,
57
- :up,
58
- :down,
59
- :message,
60
- :state,
61
- :pr,
62
- :frame,
63
- :bold,
64
- :elapsed,
65
- :subagents,
66
- :history) do
67
- def initialize(pr: nil, phase: nil, remaining: nil, frame: 0, bold: false, message: nil,
68
- elapsed: 0, subagents: 0, history: [], **rest)
69
- super
70
- end
71
-
72
- # @param history [Array<String>] newest first
73
- # @param text [String, nil]
74
- # @return [Array<String>] +text+ on top, unless it is empty or repeats
75
- # the newest line: an agent restating its activity is not news
76
- def self.remember(history, text)
77
- text = text.to_s.strip
78
- return history if text.empty? || history.first == text
79
-
80
- [text, *history].first(Board::Row::HISTORY)
81
- end
82
-
83
- # @return [Boolean]
84
- def running? = state == :running
85
- end
86
-
87
- # Statuses a row keeps, whatever the screen shows of them. A cap, so a
88
- # long-running agent's chatter cannot grow without bound. Outside the
89
- # `Data.define` block, whose constants would land on Agentilda instead.
90
- Board::Row::HISTORY = 50
91
- end