ace-overseer 0.18.1 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7357625f81ac8808666214eb562e7cfd77e103287d148b2ddd379ce360336d5f
4
- data.tar.gz: d4ef2e4c662a0ab2bf552b04e3ba9a2a27143dde016fbf511cbf1ca6d2c14b30
3
+ metadata.gz: ac2050e4e153d6bed54441f8b9a64aaea6a6bc4bcefc06413e4b7187487a20dc
4
+ data.tar.gz: e430481b7d4824d5359f7e61f3d575c3e96311af90f3f07f79eacf6952a871da
5
5
  SHA512:
6
- metadata.gz: 9e85b077290b2e41ec49e5b366004fa51c1ac4649b6593ebef8da141477c742e9ffd9adb21c7f3e48c8538d85f5441f5853df89b4413836202e6b90eb6a34fba
7
- data.tar.gz: b225d3a2dd947aeba55d60d9c1f5f4a02af4af348534afde2ba5edf86e311dfba1cff801d988698a38f33b0a01634c30c108f89607c2e1a973fe4980e8921b28
6
+ metadata.gz: 3106447b37e8b3e4faa21799da47441d1ce2b327231243148830480f507c490c86ec602b0fba2e8b1a5c1d7d584e8c7c92e26ce038b7a7a050e5cd6adf0c7801
7
+ data.tar.gz: e27c30a6e6e2cc318c05a348b6e01acbe4f6c5c692784512f1531830725edcd6210f65db5f9430f95f9fe0a9debe91732579534b091f4fc3d33f0230ed7911b6
@@ -1,5 +1,6 @@
1
1
  default_assign_preset: "work-on-task"
2
- tmux_window_presets:
2
+ runtime: auto # tmux | herdr | auto
3
+ window_presets:
3
4
  "work-on-task": "work-on-task"
4
5
  watch:
5
6
  refresh_interval: 15
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.19.0] - 2026-10-04
11
+
12
+ ### Added
13
+ - Work-on and prune manage worktree windows through the `ace-runtime` contract (`TmuxWindowOpener` replaced by `WindowOpener`); the terminal runtime comes from the new `runtime:` config key (`tmux` | `herdr` | `auto`; `ACE_RUNTIME` env wins, detection as fallback, explicit fail when nothing is live).
14
+ - Config key `tmux_window_presets` renamed to `window_presets` (no legacy alias, ADR-024); "tmux window" progress text is runtime-neutral.
15
+ - New dependency: `ace-runtime` (~> 0.1); `ace-tmux` is kept to install the tmux adapter.
16
+
17
+ ## [0.18.2] - 2026-10-02
18
+
19
+ ### Technical
20
+ - Follow the ace-assign 0.61 release line for journal-backed service effects.
21
+ - Updated the ace-tmux dependency to the 0.18 adapter release line.
22
+
10
23
  ## [0.18.1] - 2026-10-01
11
24
 
12
25
  ### Technical
data/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  ![ace-overseer demo](docs/demo/ace-overseer-getting-started.gif)
20
20
 
21
- Starting task work means creating a worktree, opening a tmux window, and preparing an assignment - three manual steps before you even begin coding. ace-overseer collapses that into a single command, tracks what is running where, and cleans up finished worktrees so nothing lingers. You can jump straight to a focused worktree any time with a single invocation.
21
+ Starting task work means creating a worktree, opening a terminal window, and preparing an assignment - three manual steps before you even begin coding. ace-overseer collapses that into a single command, tracks what is running where, and cleans up finished worktrees so nothing lingers. You can jump straight to a focused worktree any time with a single invocation.
22
22
 
23
23
  On a configured Lab machine, the same CLI can prepare and dispatch the Lab's
24
24
  system-owned `Work` objects. The default remains the existing tmux runtime;
@@ -27,8 +27,8 @@ using Lab always requires an explicit `--runtime lab` or a Lab-only command.
27
27
  ## How It Works
28
28
 
29
29
  1. Resolve task refs and create a scoped worktree.
30
- 2. Route assignment creation through `ace-assign create --task ...`, which expands the assignment preset into concrete steps under `.ace-local/assign/`, then open a dedicated tmux window mapped to that worktree.
31
- 3. Instruct the agent inside the tmux window to act as an orchestrator and drive the assignment step-by-step.
30
+ 2. Route assignment creation through `ace-assign create --task ...`, which expands the assignment preset into concrete steps under `.ace-local/assign/`, then open a dedicated terminal window mapped to that worktree.
31
+ 3. Instruct the agent inside the terminal window to act as an orchestrator and drive the assignment step-by-step.
32
32
 
33
33
  ## Use Cases
34
34
 
data/docs/usage.md CHANGED
@@ -102,6 +102,24 @@ Check dashboard: `ace-overseer status`.
102
102
 
103
103
  Preview then prune: `ace-overseer prune --dry-run`, then `ace-overseer prune --yes`.
104
104
 
105
+ ## Terminal Runtime Configuration
106
+
107
+ Work-on opens the task's terminal window and prune closes it through the
108
+ runtime-neutral `ace-runtime` contract. Select the terminal runtime in
109
+ `.ace/overseer/config.yml`:
110
+
111
+ ```yaml
112
+ # tmux | herdr | auto (default) — auto detects tmux first, then herdr;
113
+ # ACE_RUNTIME env wins over this key; nothing detected fails explicitly
114
+ runtime: auto
115
+ window_presets:
116
+ "work-on-task": "work-on-task" # preset applied to the opened window/tab
117
+ ```
118
+
119
+ The preset key is `window_presets` (renamed from `tmux_window_presets`
120
+ pre-1.0; there is no legacy alias). The same configured runtime closes a
121
+ worktree's window during accepted prunes.
122
+
105
123
  Prune migrated work with a declared destination:
106
124
 
107
125
  ```bash
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Overseer
5
+ module Atoms
6
+ # Normalizes the overseer `runtime:` config setting into a contract
7
+ # runtime name. The shipped default is `auto`, which is an
8
+ # overseer-level instruction to detect, never a contract runtime
9
+ # name; it normalizes to nil so the selector falls through to
10
+ # ACE_RUNTIME / detection.
11
+ module RuntimeSetting
12
+ module_function
13
+
14
+ def normalize(value)
15
+ normalized = value.to_s.strip
16
+ normalized.empty? || normalized == "auto" ? nil : normalized
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/runtime"
4
+
5
+ module Ace
6
+ module Overseer
7
+ module Molecules
8
+ # Opens the task's terminal window through the runtime contract.
9
+ # The window is named after the worktree basename under the shared
10
+ # naming policy and rooted at the worktree path.
11
+ class WindowOpener
12
+ def initialize(runtime: nil, config: nil, env: ENV)
13
+ @runtime = runtime
14
+ @config = config || Ace::Overseer.config
15
+ @env = env
16
+ end
17
+
18
+ def open(worktree_path:, preset: nil)
19
+ name = Ace::Runtime.sanitize_name(File.basename(worktree_path.to_s))
20
+ adapter.ensure_window(
21
+ name: name,
22
+ root: File.expand_path(worktree_path.to_s),
23
+ preset: preset
24
+ )
25
+ name
26
+ rescue Ace::Runtime::Error => e
27
+ raise Error, "Failed to open terminal window for #{worktree_path}: #{e.message}"
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :config, :env
33
+
34
+ def adapter
35
+ @adapter ||= @runtime || Ace::Runtime::Molecules::RuntimeSelector.new(
36
+ config: {runtime: Atoms::RuntimeSetting.normalize(config["runtime"])},
37
+ env: env
38
+ ).resolve
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -19,12 +19,12 @@ module Ace
19
19
  # revalidation blocks; it only suppresses the interactive confirmation
20
20
  # for already-safe candidates.
21
21
  class PruneOrchestrator
22
- def initialize(worktree_manager: nil, prune_checker: nil, tmux_executor: nil, config: nil,
22
+ def initialize(worktree_manager: nil, prune_checker: nil, runtime: nil, config: nil,
23
23
  assignment_prune_checker: nil, assignment_manager: nil, lifecycle_exclusion: nil,
24
24
  preservation_manifest_loader: nil)
25
25
  @worktree_manager = worktree_manager || Ace::Git::Worktree::Organisms::WorktreeManager.new
26
26
  @prune_checker = prune_checker || Molecules::PruneSafetyChecker.new
27
- @tmux_executor = tmux_executor || Ace::Tmux::Molecules::TmuxExecutor.new
27
+ @runtime = runtime
28
28
  @config = config || Ace::Overseer.config
29
29
  @assignment_prune_checker = assignment_prune_checker || Molecules::AssignmentPruneSafetyChecker.new
30
30
  @assignment_manager = assignment_manager || Ace::Assign::Molecules::AssignmentManager.new
@@ -168,7 +168,7 @@ module Ace
168
168
  )
169
169
  return remove_result unless remove_result[:success]
170
170
 
171
- close_tmux_window(candidate.worktree_path)
171
+ close_runtime_window(candidate.worktree_path)
172
172
  # Only the branch the recheck verified is deletable — never a
173
173
  # freshly observed branch name.
174
174
  branch_result = delete_branch(repo, branch: verified_branch, head: verified_head)
@@ -385,16 +385,21 @@ module Ace
385
385
  progress.call("Warning: failed to prune stale worktree metadata: #{e.message}")
386
386
  end
387
387
 
388
- def close_tmux_window(worktree_path)
389
- window_name = File.basename(worktree_path)
390
- tmux_bin = @config["tmux_binary"] || "tmux"
391
- session_name = @tmux_executor.run([tmux_bin, "display-message", "-p", "#S"]).to_s.strip
392
- return false if session_name.empty?
393
-
394
- @tmux_executor.run([tmux_bin, "kill-window", "-t", "#{session_name}:#{window_name}"])
395
- rescue
388
+ def close_runtime_window(worktree_path)
389
+ window_name = Ace::Runtime.sanitize_name(File.basename(worktree_path.to_s))
390
+ runtime.close_window(window: window_name)
391
+ true
392
+ rescue StandardError
393
+ # Terminal cleanup must never fail the prune; a window that
394
+ # could not be closed is left in place.
396
395
  false
397
396
  end
397
+
398
+ def runtime
399
+ @runtime ||= Ace::Runtime::Molecules::RuntimeSelector.new(
400
+ config: {runtime: Atoms::RuntimeSetting.normalize(@config["runtime"])}
401
+ ).resolve
402
+ end
398
403
  end
399
404
  end
400
405
  end
@@ -7,10 +7,10 @@ module Ace
7
7
  # B36TS subtask pattern: "8pp.t.q7w.a" (parent 9-char ID + dot + single char)
8
8
  SUBTASK_PATTERN = /^[0-9a-z]{3}\.[a-z]\.[0-9a-z]{3}\.[a-z0-9]$/
9
9
 
10
- def initialize(worktree_provisioner: nil, tmux_window_opener: nil, assignment_launcher: nil,
10
+ def initialize(worktree_provisioner: nil, window_opener: nil, assignment_launcher: nil,
11
11
  task_loader: nil, config: nil, assignment_detector: nil, lifecycle_exclusion: nil)
12
12
  @worktree_provisioner = worktree_provisioner || Molecules::WorktreeProvisioner.new
13
- @tmux_window_opener = tmux_window_opener || Molecules::TmuxWindowOpener.new
13
+ @window_opener = window_opener || Molecules::WindowOpener.new
14
14
  @task_manager = task_loader || Ace::Task::Organisms::TaskManager.new
15
15
  @assignment_launcher = assignment_launcher || Molecules::AssignmentLauncher.new(task_manager: @task_manager)
16
16
  @config = config || Ace::Overseer.config
@@ -47,7 +47,7 @@ module Ace
47
47
 
48
48
  expanded_taskrefs = expand_task_refs_in_order(resolved_refs)
49
49
  primary_subtask_refs = extract_subtask_refs(primary_task)
50
- tmux_preset = @config.dig("tmux_window_presets", preset_name)
50
+ window_preset = @config.dig("window_presets", preset_name)
51
51
 
52
52
  # Participate in prune exclusion for the whole start: provisioning,
53
53
  # runtime window and assignment launch all hold the shared side of
@@ -67,10 +67,10 @@ module Ace
67
67
  progress.call("Worktree exists at #{worktree[:worktree_path]}")
68
68
  end
69
69
 
70
- progress.call("Opening tmux window...")
71
- @tmux_window_opener.open(
70
+ progress.call("Opening terminal window...")
71
+ @window_opener.open(
72
72
  worktree_path: worktree[:worktree_path],
73
- preset: tmux_preset
73
+ preset: window_preset
74
74
  )
75
75
 
76
76
  progress.call("Checking assignment status...")
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Overseer
5
- VERSION = "0.18.1"
5
+ VERSION = "0.19.0"
6
6
  end
7
7
  end
data/lib/ace/overseer.rb CHANGED
@@ -6,7 +6,7 @@ require "ace/assign"
6
6
  require "ace/git"
7
7
  require "ace/git/worktree"
8
8
  require "ace/task"
9
- require "ace/tmux"
9
+ require "ace/runtime"
10
10
 
11
11
  module Ace
12
12
  module Overseer
@@ -21,10 +21,11 @@ require_relative "overseer/models/assignment_prune_candidate"
21
21
  require_relative "overseer/models/preservation_proof"
22
22
  require_relative "overseer/atoms/repo_guard"
23
23
  require_relative "overseer/atoms/preset_resolver"
24
+ require_relative "overseer/atoms/runtime_setting"
24
25
  require_relative "overseer/atoms/status_formatter"
25
26
  require_relative "overseer/molecules/worktree_provisioner"
26
27
  require_relative "overseer/molecules/lab_client"
27
- require_relative "overseer/molecules/tmux_window_opener"
28
+ require_relative "overseer/molecules/window_opener"
28
29
  require_relative "overseer/molecules/assignment_launcher"
29
30
  require_relative "overseer/molecules/worktree_context_collector"
30
31
  require_relative "overseer/molecules/preservation_manifest"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-overseer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.1
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-01 00:00:00.000000000 Z
10
+ date: 2026-10-04 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-cli
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '0.60'
74
+ version: '0.62'
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '0.60'
81
+ version: '0.62'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: ace-git
84
84
  requirement: !ruby/object:Gem::Requirement
@@ -99,14 +99,14 @@ dependencies:
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '0.22'
102
+ version: '0.25'
103
103
  type: :runtime
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '0.22'
109
+ version: '0.25'
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: ace-task
112
112
  requirement: !ruby/object:Gem::Requirement
@@ -121,20 +121,34 @@ dependencies:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0.37'
124
+ - !ruby/object:Gem::Dependency
125
+ name: ace-runtime
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '0.1'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.1'
124
138
  - !ruby/object:Gem::Dependency
125
139
  name: ace-tmux
126
140
  requirement: !ruby/object:Gem::Requirement
127
141
  requirements:
128
142
  - - "~>"
129
143
  - !ruby/object:Gem::Version
130
- version: '0.17'
144
+ version: '0.18'
131
145
  type: :runtime
132
146
  prerelease: false
133
147
  version_requirements: !ruby/object:Gem::Requirement
134
148
  requirements:
135
149
  - - "~>"
136
150
  - !ruby/object:Gem::Version
137
- version: '0.17'
151
+ version: '0.18'
138
152
  - !ruby/object:Gem::Dependency
139
153
  name: ace-support-test-helpers
140
154
  requirement: !ruby/object:Gem::Requirement
@@ -220,6 +234,7 @@ files:
220
234
  - lib/ace/overseer.rb
221
235
  - lib/ace/overseer/atoms/preset_resolver.rb
222
236
  - lib/ace/overseer/atoms/repo_guard.rb
237
+ - lib/ace/overseer/atoms/runtime_setting.rb
223
238
  - lib/ace/overseer/atoms/status_formatter.rb
224
239
  - lib/ace/overseer/cli.rb
225
240
  - lib/ace/overseer/cli/commands/agents.rb
@@ -242,7 +257,7 @@ files:
242
257
  - lib/ace/overseer/molecules/lab_prune_safety_checker.rb
243
258
  - lib/ace/overseer/molecules/preservation_manifest.rb
244
259
  - lib/ace/overseer/molecules/prune_safety_checker.rb
245
- - lib/ace/overseer/molecules/tmux_window_opener.rb
260
+ - lib/ace/overseer/molecules/window_opener.rb
246
261
  - lib/ace/overseer/molecules/worktree_context_collector.rb
247
262
  - lib/ace/overseer/molecules/worktree_provisioner.rb
248
263
  - lib/ace/overseer/organisms/prune_orchestrator.rb
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "ace/tmux"
4
-
5
- module Ace
6
- module Overseer
7
- module Molecules
8
- class TmuxWindowOpener
9
- def initialize(tmux_window_command: nil, tmux_executor: nil)
10
- @tmux_window_command = tmux_window_command || Ace::Tmux::CLI::Commands::Window.new
11
- @tmux_executor = tmux_executor || Ace::Tmux::Molecules::TmuxExecutor.new
12
- end
13
-
14
- def open(worktree_path:, preset: nil)
15
- ensure_session_exists
16
- return if window_already_open?(worktree_path)
17
-
18
- @tmux_window_command.call(
19
- root: worktree_path.to_s,
20
- preset: preset,
21
- quiet: true,
22
- session: ENV["ACE_TMUX_SESSION"]
23
- )
24
- end
25
-
26
- private
27
-
28
- def ensure_session_exists
29
- session = ENV["ACE_TMUX_SESSION"]
30
- return unless session && !session.empty?
31
- return if @tmux_executor.capture(["tmux", "has-session", "-t", session]).success?
32
-
33
- created = @tmux_executor.run(["tmux", "new-session", "-d", "-s", session])
34
- raise "Failed to create tmux session '#{session}'" unless created
35
- end
36
-
37
- def window_already_open?(worktree_path)
38
- session = ENV["ACE_TMUX_SESSION"]
39
- return false unless session
40
-
41
- name = Ace::Tmux::Atoms::WindowNameSanitizer.call(File.basename(worktree_path.to_s))
42
- result = @tmux_executor.capture(["tmux", "list-windows", "-t", session, "-F", '#{window_name}'])
43
- return false unless result.success?
44
-
45
- result.stdout.split("\n").any? { |w| w.strip == name }
46
- rescue
47
- false
48
- end
49
- end
50
- end
51
- end
52
- end