ace-herdr 0.9.0 → 0.10.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: f3c832e03a65b1e294f84d843966554e2018db81ec39a13ed508d4f68bb8ae07
4
- data.tar.gz: 2c7170d346c932c50c2d881fb528733bee63b2fa79073340ad64a8c2036f8e9e
3
+ metadata.gz: 204e5cf8135c02f3feea7086b05a2b3dad61470261dbd526a05b900dd1f5e558
4
+ data.tar.gz: b362791611a61f9340055ad044383dd4585b2d5821ffc1d28a4e7356dd6ab6b1
5
5
  SHA512:
6
- metadata.gz: c9c513a7de5c8224c73e597f133b70ed6a99aa70f70292d7c1774b789ac3a2b570f5452f19e35cbd780c29fb392edd683da46ddf2a3256035bd911bc16533cbd
7
- data.tar.gz: 071d1908f3bd701b9b516d5d7100f68a830b7eb2cd153129660c66afb08730e2f135127daf68326186479c93e6a58d4de294361293f07d561fb156aa6d19881a
6
+ metadata.gz: 8c564329116783969ae79dc7256f8a414591e308d18b3b8772b751ac1efea5d07b8f4758ee34b23134b50631a571e01d48d7872b20c8510fb637d019f6889b42
7
+ data.tar.gz: 7a1be61896fff3f5ca58c7ee00d2c4fa0633e935805c2cd466ceaad12b4a4a3bf04cbf02d7cca7bd43bf3edf28196feb190b14e3282fe736a8e6137e5918a45f
data/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to `ace-herdr` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.10.0] - 2026-11-10
11
+
12
+ ### Added
13
+
14
+ - Launch ordinary agents with an explicit private JSON environment file, without exposing credentials in terminal commands. Preserve real pane/session identity and refuse cold auto-resume without the original environment. Retain protected installed extensions and package reproducible native source builds for Linux ARM64, including cross-building on macOS.
15
+
8
16
  ## [0.9.0] - 2026-10-30
9
17
 
10
18
  ### Changed
@@ -0,0 +1,44 @@
1
+ # Building the selected native Herdr
2
+
3
+ `ace-herdr native-source selection` reports the source commit, tree and patch
4
+ digest packaged with this gem. The patch retains ordinary prompt/terminal
5
+ identity fixes and adds explicit process environments. It does not install an
6
+ issuer, systemd session launcher or separate operating-system identity.
7
+
8
+ Build with the public owner command:
9
+
10
+ ```sh
11
+ ace-herdr native-source build --source /absolute/clean/herdr-checkout \
12
+ --output /absolute/new/artifact-directory --target aarch64-unknown-linux-musl
13
+ ace-herdr native-source verify --artifact /absolute/new/artifact-directory
14
+ ```
15
+
16
+ The build runs as a nonroot user on Linux or macOS. Install the selected Rust
17
+ toolchain, its target standard library, and the selected Zig version first.
18
+ Use actual tool executables in `PATH`, rather than shims which resolve another
19
+ version. The packaged linker invokes that same pinned Zig for the selected
20
+ Linux target; a VM and a separate cross compiler are unnecessary. Output
21
+ contains the ELF executable and its source/toolchain/byte provenance. A failed
22
+ build retains its output directory for diagnosis and never overwrites it.
23
+
24
+ The selected native CLI accepts `herdr agent start … --env-file /absolute/env.json`.
25
+ The file must be a regular, non-symlink file owned by the process user with mode
26
+ `0600`, containing a JSON object of environment variable names and string values.
27
+ The ordinary child process receives that environment, plus native terminal
28
+ identity (`HERDR_*` pane/workspace/socket fields and the ACE pane/session aliases).
29
+ Ambient credentials are cleared. File contents are never submitted to the
30
+ terminal or returned in the start response. The caller removes the file after
31
+ startup returns. With `ACE_SESSION_CREDENTIALS_DIRECTORY` equal to
32
+ `PI_CODING_AGENT_DIR`, a same-user `0700` directory named `ace-session-*`, the
33
+ ordinary helper waits for the child and removes only `auth.json` on exit or
34
+ failed startup. Session evidence remains. TERM/HUP are forwarded to the child;
35
+ terminal Ctrl-C reaches its existing foreground process group. An uncatchable
36
+ kill can leave credentials behind, so the assignment/coordinator owner must
37
+ reconcile them before replacing a dead session. This is cleanup between trusted
38
+ processes, not isolation between hostile processes sharing the same user.
39
+
40
+ Cold server restoration keeps such a pane visibly unavailable until its
41
+ coordinator supplies a fresh environment. It never resumes an agent through
42
+ an ambient shell. Live terminal handoff preserves the already-running process.
43
+
44
+ Host tests and a cross-built ELF do not establish live ARM64 lab acceptance.
data/docs/usage.md CHANGED
@@ -70,6 +70,24 @@ by the number of deliveries.
70
70
 
71
71
  ## Dispatch, send, capture, wait, close
72
72
 
73
+ `dispatch` starts an ordinary process in `--cwd`. For an explicitly selected
74
+ environment, pass `--env-file` with an absolute path to a private (`0600`) JSON
75
+ object of string variables. The native launcher clears ambient variables and
76
+ reads this file without placing its values in terminal commands or prompts.
77
+ `--role` labels the commission; it does not grant operating-system privileges.
78
+
79
+ ```sh
80
+ ace-herdr dispatch --label review-task --role verifier --cwd /lab/ace/.ace-wt/task-1 --env-file /private/role-environment.json --prompt-file commission.md
81
+ ```
82
+
83
+ Installed Linux deployments use the protected extensions configured by
84
+ `ace-lab`. Missing required runtime or extension files block startup. Sessions
85
+ of the same user are trusted peers, not a hostile-code sandbox. For task work,
86
+ use `ace-overseer work-on` or `handoff`: these own commission identity, duplicate
87
+ protection and report-driven retirement. A low-level dispatch alone is not an
88
+ assignment lifecycle.
89
+
90
+
73
91
  ```bash
74
92
  ace-herdr dispatch --label t.123 --kind pi --prompt-file prompt.md
75
93
  ace-herdr dispatch --label review --pane p7 --cwd /Users/me/project
@@ -87,6 +105,11 @@ prompt semantics: text becomes one self-submitting agent prompt, and a single
87
105
  trailing Enter is dropped and reported. A command must be declared before any
88
106
  key.
89
107
 
108
+ For the native `herdr agent prompt` command, `--wait` without `--until`
109
+ waits for a settled state. Use `--wait --until working --until blocked` to
110
+ wait for observed activity instead. A stalled or timed-out prompt has an
111
+ uncertain delivery outcome; inspect the existing agent before retrying.
112
+
90
113
  ## Workspaces, tabs, and presets
91
114
 
92
115
  Presets resolve through the ADR-022 cascade (`.ace/herdr/` > `~/.ace/herdr/` >
@@ -26,6 +26,8 @@ module Ace
26
26
  ]
27
27
 
28
28
  option :label, type: :string, desc: "Agent and pane label (typically the task id)"
29
+ option :role, type: :string, desc: "Executor role label"
30
+ option :env_file, type: :string, desc: "Private JSON file containing the executor environment"
29
31
  option :kind, type: :string, desc: "Agent kind (default: config default_agent_kind)"
30
32
  option :workspace, type: :string, desc: "Target workspace (default: caller's workspace)"
31
33
  option :pane, type: :string, desc: "Existing pane (default: create a new tab)"
@@ -50,7 +52,7 @@ module Ace
50
52
  outcome = dispatcher.dispatch(
51
53
  label: options.fetch(:label), prompt: prompt,
52
54
  kind: options[:kind], workspace_id: options[:workspace],
53
- pane: options[:pane], cwd: options[:cwd]
55
+ pane: options[:pane], cwd: options[:cwd], role: options[:role], env_file: options[:env_file]
54
56
  )
55
57
  puts JSON.generate(outcome.to_h)
56
58
  end
@@ -5,6 +5,7 @@ require "open3"
5
5
  require "json"
6
6
  require "timeout"
7
7
  require_relative "bounded_process"
8
+ require_relative "installed_pi_extensions"
8
9
 
9
10
  module Ace
10
11
  module Herdr
@@ -23,6 +24,10 @@ module Ace
23
24
  @process = process
24
25
  end
25
26
 
27
+ def agent_list
28
+ run!([@binary, "agent", "list"])
29
+ end
30
+
26
31
  # Probe the agent living in a pane (raises AgentNotFoundError when none)
27
32
  def agent_get(pane)
28
33
  run!([@binary, "agent", "get", pane])
@@ -55,9 +60,13 @@ module Ace
55
60
  # `args` are harness passthrough arguments forwarded verbatim
56
61
  # after `--` (e.g. pi's --model/--thinking/--append-system-prompt;
57
62
  # the overseer start contract, 8xs.t.pu0.1).
58
- def agent_start(name:, kind:, pane:, timeout_ms:, args: [])
63
+ def agent_start(name:, kind:, pane:, timeout_ms:, args: [], env_file: nil)
64
+ managed = kind == "pi" ? InstalledPiExtensions.arguments : []
65
+ InstalledPiExtensions.reject_override!(args) unless managed.empty?
66
+ args = managed + args
59
67
  cmd = [@binary, "agent", "start", name, "--kind", kind,
60
68
  "--pane", pane, "--timeout", timeout_ms.to_s]
69
+ cmd += ["--env-file", env_file] if env_file
61
70
  cmd += ["--", *args] unless args.empty?
62
71
  run!(cmd)
63
72
  end
@@ -66,12 +75,14 @@ module Ace
66
75
  # rejects blocked agents pre-send with agent_blocked. With
67
76
  # wait: herdr's activity gate applies — a prompt sent from a
68
77
  # non-working state must produce observed working/blocked
69
- # activity, so success confirms the turn actually began
70
- # (agent_prompt_stalled / timeout otherwise; timeout_ms bounds
71
- # the wait).
72
- def agent_prompt(pane:, text:, wait: false, timeout_ms: nil)
78
+ # activity before waiting for the requested state. With no explicit
79
+ # states the native default waits for idle, done or blocked.
80
+ def agent_prompt(pane:, text:, wait: false, timeout_ms: nil, until_states: nil)
81
+ raise ArgumentError, "until_states requires wait" if until_states && !wait
82
+
73
83
  cmd = [@binary, "agent", "prompt", pane, text]
74
84
  cmd += ["--wait"] if wait
85
+ Array(until_states).each { |state| cmd += ["--until", state] }
75
86
  cmd += ["--timeout", timeout_ms.to_s] if timeout_ms
76
87
  run!(cmd)
77
88
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "find"
4
+ require "yaml"
5
+ require "ace/runtime"
6
+ require "ace/runtime/molecules/protected_socket"
7
+ require_relative "../errors"
8
+
9
+ module Ace
10
+ module Herdr
11
+ module Molecules
12
+ # Installed Linux lab agents load only host-managed Pi extensions.
13
+ # The existing root-owned lab manifest declares their location; no
14
+ # second registry or agent-writable discovery path is introduced.
15
+ module InstalledPiExtensions
16
+ MANIFEST_DIR = "/etc/lab/ace-lab"
17
+ MANIFEST = "#{MANIFEST_DIR}/install.yml"
18
+ module_function
19
+
20
+ def arguments(env: ENV, manifest: MANIFEST, manifest_dir: MANIFEST_DIR, platform: RUBY_PLATFORM)
21
+ return [] unless env["ACE_TOOLCHAIN"] == "installed" && platform.include?("linux")
22
+ return [] unless File.exist?(manifest_dir) || File.symlink?(manifest_dir)
23
+
24
+ guard = Ace::Runtime::Molecules::ProtectedSocket
25
+ guard.root_path!(manifest, directory: false)
26
+ raise ExecutorUnavailableError, "installed lab manifest is not a regular file" unless File.file?(manifest)
27
+ document = YAML.safe_load(File.read(manifest), permitted_classes: [], aliases: false)
28
+ rows = document.is_a?(Hash) && document.dig("gemset", "extensions")
29
+ matches = rows.is_a?(Array) ? rows.select { |row| row.is_a?(Hash) && row["gem"] == "ace-wake" } : []
30
+ raise ExecutorUnavailableError, "installed lab must declare exactly one ace-wake extension path" unless matches.size == 1
31
+ root = matches.fetch(0)["path"]
32
+ guard.root_path!(root, directory: true)
33
+ wake = File.join(root, "ace-wake")
34
+ guard.root_path!(wake, directory: true)
35
+ Find.find(wake) do |path|
36
+ stat = File.lstat(path)
37
+ unless (stat.file? || stat.directory?) && !stat.symlink? && stat.uid.zero? && (stat.mode & 0o022).zero?
38
+ raise ExecutorUnavailableError, "installed ace-wake contains an unsafe path"
39
+ end
40
+ end
41
+ entry = File.join(wake, "index.js")
42
+ integration = File.join(root, "herdr-agent-state.ts")
43
+ [entry, integration].each do |path|
44
+ guard.root_path!(path, directory: false)
45
+ raise ExecutorUnavailableError, "installed Pi extension is not a regular file" unless File.file?(path)
46
+ end
47
+ ["--no-extensions", "--extension", entry, "--extension", integration]
48
+ rescue Ace::Runtime::Error, SystemCallError, Psych::Exception, TypeError, ArgumentError
49
+ raise ExecutorUnavailableError, "installed Pi extension configuration is unavailable or unsafe"
50
+ end
51
+
52
+ def reject_override!(args)
53
+ return if args.none? { |arg| arg.is_a?(String) && (arg == "--no-extensions" || arg == "-ne" || arg.start_with?("--extension=") || arg == "--extension" || arg.start_with?("-e")) }
54
+
55
+ raise ExecutorUnavailableError, "installed lab Pi extension arguments cannot be overridden"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end