harnex 0.7.9 → 0.7.10
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/CHANGELOG.md +22 -0
- data/README.md +16 -0
- data/guides/01_dispatch.md +16 -0
- data/lib/harnex/artifact_report.rb +221 -0
- data/lib/harnex/commands/run.rb +29 -4
- data/lib/harnex/runtime/session.rb +28 -3
- data/lib/harnex/version.rb +1 -1
- data/lib/harnex.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2af8afe53ccea3fb668e920809da3fdbcd523ee5eef0179bcdd84f026077cff1
|
|
4
|
+
data.tar.gz: f675d4a401f703bf52adc88ce352fec04d2090533c3249bd0ac05e6828d030f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cda7d84fd1f80c9526529b03293623eb16f76a78872e5e29640896be76f79a448ab3bf8f4c92c74c56b8f88b814739b7fbd201a6c91ba749d22ecdc5cf8a720e
|
|
7
|
+
data.tar.gz: bc6704fc2a86a01e61c1a431fd96781bbaf178029679bc1b5c64be699f82c49bbf8b27d4f921c0d7547752eadc2dc61cffc1bc946b99d0cd5d79dd46a0de77af
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.7.10] - 2026-07-10 | 10:57 PM | IST
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `harnex run --artifact-report PATH` / `--validation-report PATH` now expose a
|
|
10
|
+
worker-writable `harnex.artifact_report.v1` sidecar path via
|
|
11
|
+
`HARNEX_ARTIFACT_REPORT_PATH` and `HARNEX_VALIDATION_REPORT_PATH`, ingest the
|
|
12
|
+
report at dispatch finalization, and append compact top-level
|
|
13
|
+
`artifact_report`, `validation`, and `artifacts` blocks to the dispatch
|
|
14
|
+
summary row.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Dispatch telemetry docs, README, and the packaged dispatch agents-guide now
|
|
19
|
+
document sidecar proof while keeping plain-text `koder/` artifacts canonical.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Missing, malformed, unsupported-schema, and oversized artifact reports now
|
|
24
|
+
fail soft with `artifact_report.ingest_status` warning telemetry instead of
|
|
25
|
+
crashing or changing the wrapped process exit code.
|
|
26
|
+
|
|
5
27
|
## [0.7.9] - 2026-07-10 | 10:33 PM | IST
|
|
6
28
|
|
|
7
29
|
### Added
|
data/README.md
CHANGED
|
@@ -268,6 +268,22 @@ Dispatch briefs can declare soft budget metadata through `--meta`:
|
|
|
268
268
|
harnex run pi --meta '{"read_budget_lines":2000,"output_ceiling_lines":800}' ...
|
|
269
269
|
```
|
|
270
270
|
|
|
271
|
+
Workers can also write a small machine-readable proof sidecar while keeping the
|
|
272
|
+
canonical explanation in plain-text `koder/` files:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
harnex run pi --id pi-i-52 \
|
|
276
|
+
--artifact-report .harnex/reports/pi-i-52.json \
|
|
277
|
+
--context 'Run validation, update koder/issues/52.md, and write JSON proof to $HARNEX_ARTIFACT_REPORT_PATH' \
|
|
278
|
+
--auto-stop
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The sidecar schema is `harnex.artifact_report.v1`; harnex exposes the path as
|
|
282
|
+
`HARNEX_ARTIFACT_REPORT_PATH` / `HARNEX_VALIDATION_REPORT_PATH`, then records a
|
|
283
|
+
compact `artifact_report`, `validation`, and `artifacts` summary in the dispatch
|
|
284
|
+
row. Missing or malformed reports are warning telemetry, not wrapped-process
|
|
285
|
+
crashes.
|
|
286
|
+
|
|
271
287
|
Those declared values are copied into summary `meta`. Terminal summary
|
|
272
288
|
`actual` records timing, exit classification, token usage when the adapter can
|
|
273
289
|
capture it, adapter-reported `cost_usd` when reliably available, git deltas,
|
data/guides/01_dispatch.md
CHANGED
|
@@ -95,6 +95,22 @@ harnex run codex --cwd /tmp/leximaze_eval_run_001 \
|
|
|
95
95
|
`--root DIR` only overrides harnex's root attribution; it does not change the
|
|
96
96
|
child process cwd. Neither flag is a sandbox.
|
|
97
97
|
|
|
98
|
+
For queue closeout, ask workers to write a compact sidecar in addition to their
|
|
99
|
+
plain-text `koder/` artifact:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
harnex run pi --id pi-i-NN --tmux pi-i-NN \
|
|
103
|
+
--artifact-report .harnex/reports/pi-i-NN.json \
|
|
104
|
+
--context 'Update the canonical koder file and write harnex.artifact_report.v1 proof to $HARNEX_ARTIFACT_REPORT_PATH' \
|
|
105
|
+
--auto-stop
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The worker should keep the full explanation in `koder/` and put only compact
|
|
109
|
+
machine-readable proof in the sidecar: validation command/status, typed
|
|
110
|
+
artifact summaries (`finding`, `review`, `gate`, `blocker`, etc.), evidence,
|
|
111
|
+
confidence, and canonical refs. Harnex records missing/malformed sidecars as
|
|
112
|
+
warning telemetry instead of failing the wrapped process.
|
|
113
|
+
|
|
98
114
|
Pi runs use structured RPC (`pi --mode rpc`). Pass Pi child flags after `--`
|
|
99
115
|
(e.g. `harnex run pi --context "..." -- --model anthropic/claude-sonnet-4-5 --thinking high`).
|
|
100
116
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module Harnex
|
|
5
|
+
module ArtifactReport
|
|
6
|
+
SCHEMA = "harnex.artifact_report.v1"
|
|
7
|
+
MAX_BYTES = 256 * 1024
|
|
8
|
+
MAX_ARTIFACTS = 50
|
|
9
|
+
MAX_COMMANDS = 50
|
|
10
|
+
MAX_CANONICAL_ARTIFACTS = 50
|
|
11
|
+
MAX_EVIDENCE_ITEMS = 20
|
|
12
|
+
MAX_STRING_LENGTH = 2_000
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def ingest(path)
|
|
17
|
+
report_path = File.expand_path(path.to_s)
|
|
18
|
+
return missing(report_path) unless File.file?(report_path)
|
|
19
|
+
|
|
20
|
+
bytes = File.size(report_path)
|
|
21
|
+
sha256 = file_sha256(report_path)
|
|
22
|
+
if bytes > MAX_BYTES
|
|
23
|
+
return warning(
|
|
24
|
+
report_path,
|
|
25
|
+
bytes: bytes,
|
|
26
|
+
sha256: sha256,
|
|
27
|
+
ingest_status: "oversized",
|
|
28
|
+
warning: "artifact report is #{bytes} bytes; max is #{MAX_BYTES} bytes"
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
parsed = JSON.parse(File.read(report_path, mode: "rb"))
|
|
33
|
+
unless parsed.is_a?(Hash)
|
|
34
|
+
return warning(
|
|
35
|
+
report_path,
|
|
36
|
+
bytes: bytes,
|
|
37
|
+
sha256: sha256,
|
|
38
|
+
ingest_status: "malformed",
|
|
39
|
+
warning: "artifact report must be a JSON object"
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
schema = parsed["schema"].to_s
|
|
44
|
+
unless schema == SCHEMA
|
|
45
|
+
return warning(
|
|
46
|
+
report_path,
|
|
47
|
+
bytes: bytes,
|
|
48
|
+
sha256: sha256,
|
|
49
|
+
ingest_status: "unsupported_schema",
|
|
50
|
+
schema: schema.empty? ? nil : bounded_string(schema),
|
|
51
|
+
warning: "unsupported artifact report schema #{schema.inspect}; expected #{SCHEMA}"
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
build_payload(report_path, bytes: bytes, sha256: sha256, report: parsed)
|
|
56
|
+
rescue JSON::ParserError => e
|
|
57
|
+
warning(
|
|
58
|
+
report_path,
|
|
59
|
+
bytes: safe_file_size(report_path),
|
|
60
|
+
sha256: safe_file_sha256(report_path),
|
|
61
|
+
ingest_status: "malformed",
|
|
62
|
+
warning: "malformed artifact report JSON: #{bounded_string(e.message)}"
|
|
63
|
+
)
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
warning(
|
|
66
|
+
report_path,
|
|
67
|
+
bytes: safe_file_size(report_path),
|
|
68
|
+
sha256: safe_file_sha256(report_path),
|
|
69
|
+
ingest_status: "error",
|
|
70
|
+
warning: "artifact report ingest failed: #{bounded_string(e.message)}"
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def build_payload(path, bytes:, sha256:, report:)
|
|
75
|
+
artifacts = compact_artifacts(report["artifacts"])
|
|
76
|
+
validation = compact_validation(report["validation"])
|
|
77
|
+
payload = {
|
|
78
|
+
"artifact_report" => metadata(
|
|
79
|
+
path,
|
|
80
|
+
bytes: bytes,
|
|
81
|
+
sha256: sha256,
|
|
82
|
+
ingest_status: "ok",
|
|
83
|
+
schema: SCHEMA
|
|
84
|
+
).merge(
|
|
85
|
+
"report_status" => bounded_string_or_nil(report["status"]),
|
|
86
|
+
"canonical_artifacts" => string_array(report["canonical_artifacts"], max_items: MAX_CANONICAL_ARTIFACTS),
|
|
87
|
+
"artifact_count" => artifacts.length
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
payload["validation"] = validation if validation
|
|
91
|
+
payload["artifacts"] = artifacts unless artifacts.empty?
|
|
92
|
+
payload
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def missing(path)
|
|
96
|
+
warning(
|
|
97
|
+
path,
|
|
98
|
+
bytes: nil,
|
|
99
|
+
sha256: nil,
|
|
100
|
+
ingest_status: "missing",
|
|
101
|
+
warning: "artifact report not found"
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def warning(path, bytes:, sha256:, ingest_status:, warning:, schema: nil)
|
|
106
|
+
report = metadata(path, bytes: bytes, sha256: sha256, ingest_status: ingest_status, schema: schema)
|
|
107
|
+
report["warning"] = bounded_string(warning)
|
|
108
|
+
{ "artifact_report" => report }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def metadata(path, bytes:, sha256:, ingest_status:, schema: nil)
|
|
112
|
+
{
|
|
113
|
+
"path" => path.to_s,
|
|
114
|
+
"bytes" => bytes,
|
|
115
|
+
"sha256" => sha256,
|
|
116
|
+
"ingest_status" => ingest_status,
|
|
117
|
+
"schema" => schema
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def compact_validation(value)
|
|
122
|
+
return nil unless value.is_a?(Hash)
|
|
123
|
+
|
|
124
|
+
payload = {}
|
|
125
|
+
payload["status"] = bounded_string_or_nil(value["status"])
|
|
126
|
+
payload["commands"] = compact_commands(value["commands"])
|
|
127
|
+
payload["final_reported"] = !!value["final_reported"] if value.key?("final_reported")
|
|
128
|
+
payload.delete_if { |_key, v| v.nil? || v == [] }
|
|
129
|
+
payload.empty? ? nil : payload
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def compact_commands(value)
|
|
133
|
+
Array(value).first(MAX_COMMANDS).filter_map do |entry|
|
|
134
|
+
next unless entry.is_a?(Hash)
|
|
135
|
+
|
|
136
|
+
compact = {
|
|
137
|
+
"cmd" => bounded_string_or_nil(entry["cmd"]),
|
|
138
|
+
"exit_code" => integer_or_nil(entry["exit_code"]),
|
|
139
|
+
"status" => bounded_string_or_nil(entry["status"]),
|
|
140
|
+
"duration_s" => finite_float_or_nil(entry["duration_s"])
|
|
141
|
+
}
|
|
142
|
+
compact.delete_if { |_key, v| v.nil? }
|
|
143
|
+
compact.empty? ? nil : compact
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def compact_artifacts(value)
|
|
148
|
+
Array(value).first(MAX_ARTIFACTS).filter_map do |entry|
|
|
149
|
+
next unless entry.is_a?(Hash)
|
|
150
|
+
|
|
151
|
+
compact = {
|
|
152
|
+
"type" => bounded_string_or_nil(entry["type"]),
|
|
153
|
+
"summary" => bounded_string_or_nil(entry["summary"]),
|
|
154
|
+
"evidence" => string_array(entry["evidence"], max_items: MAX_EVIDENCE_ITEMS),
|
|
155
|
+
"confidence" => finite_float_or_nil(entry["confidence"]),
|
|
156
|
+
"canonical_ref" => bounded_string_or_nil(entry["canonical_ref"])
|
|
157
|
+
}
|
|
158
|
+
compact.delete_if { |_key, v| v.nil? || v == [] }
|
|
159
|
+
compact.empty? ? nil : compact
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def string_array(value, max_items:)
|
|
164
|
+
Array(value).first(max_items).filter_map do |item|
|
|
165
|
+
text = bounded_string_or_nil(item)
|
|
166
|
+
text unless text.nil? || text.empty?
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def bounded_string_or_nil(value)
|
|
171
|
+
return nil if value.nil?
|
|
172
|
+
|
|
173
|
+
bounded_string(value)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def bounded_string(value)
|
|
177
|
+
text = value.to_s
|
|
178
|
+
return text if text.length <= MAX_STRING_LENGTH
|
|
179
|
+
|
|
180
|
+
text[0, MAX_STRING_LENGTH]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def integer_or_nil(value)
|
|
184
|
+
return nil if value.nil?
|
|
185
|
+
|
|
186
|
+
Integer(value)
|
|
187
|
+
rescue ArgumentError, TypeError
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def finite_float_or_nil(value)
|
|
192
|
+
return nil if value.nil?
|
|
193
|
+
|
|
194
|
+
number = Float(value)
|
|
195
|
+
number.finite? ? number : nil
|
|
196
|
+
rescue ArgumentError, TypeError
|
|
197
|
+
nil
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def file_sha256(path)
|
|
201
|
+
digest = Digest::SHA256.new
|
|
202
|
+
File.open(path, "rb") do |file|
|
|
203
|
+
buffer = +""
|
|
204
|
+
digest.update(buffer) while file.read(16 * 1024, buffer)
|
|
205
|
+
end
|
|
206
|
+
digest.hexdigest
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def safe_file_size(path)
|
|
210
|
+
File.size(path) if File.file?(path)
|
|
211
|
+
rescue StandardError
|
|
212
|
+
nil
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def safe_file_sha256(path)
|
|
216
|
+
file_sha256(path) if File.file?(path)
|
|
217
|
+
rescue StandardError
|
|
218
|
+
nil
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
data/lib/harnex/commands/run.rb
CHANGED
|
@@ -8,11 +8,11 @@ module Harnex
|
|
|
8
8
|
KNOWN_FLAGS = %w[
|
|
9
9
|
--id --description --detach --tmux --host --port --watch --watch-file
|
|
10
10
|
--stall-after --max-resumes --preset --context --meta --summary-out
|
|
11
|
-
--cwd --root --timeout --inbox-ttl --auto-stop --fast --legacy-pty --help
|
|
11
|
+
--artifact-report --validation-report --cwd --root --timeout --inbox-ttl --auto-stop --fast --legacy-pty --help
|
|
12
12
|
].freeze
|
|
13
13
|
VALUE_FLAGS = %w[
|
|
14
14
|
--id --description --host --port --watch --watch-file --stall-after
|
|
15
|
-
--max-resumes --preset --context --meta --summary-out --cwd --root --timeout --inbox-ttl
|
|
15
|
+
--max-resumes --preset --context --meta --summary-out --artifact-report --validation-report --cwd --root --timeout --inbox-ttl
|
|
16
16
|
].freeze
|
|
17
17
|
|
|
18
18
|
def self.usage(program_name = "harnex run")
|
|
@@ -37,6 +37,10 @@ module Harnex
|
|
|
37
37
|
Default Codex runs force service_tier="flex".
|
|
38
38
|
--meta JSON Attach parsed JSON metadata to the started event
|
|
39
39
|
--summary-out PATH Append dispatch telemetry summary JSONL to PATH
|
|
40
|
+
--artifact-report PATH
|
|
41
|
+
Worker-written harnex.artifact_report.v1 JSON sidecar to ingest at exit
|
|
42
|
+
--validation-report PATH
|
|
43
|
+
Alias for --artifact-report; also exposed as HARNEX_VALIDATION_REPORT_PATH
|
|
40
44
|
--cwd DIR Run the wrapped agent from DIR and use DIR as the session root
|
|
41
45
|
--root DIR Override harnex session/root attribution without changing child cwd
|
|
42
46
|
--timeout SECS Max seconds to wait for detached registration (default: #{DEFAULT_TIMEOUT})
|
|
@@ -61,6 +65,7 @@ module Harnex
|
|
|
61
65
|
#{program_name} pi --id pi-i-42 --tmux pi-i-42 --context "Read /tmp/task-impl-42.md" --auto-stop
|
|
62
66
|
#{program_name} pi --id pi-i-42 --watch --preset impl --context "Read /tmp/task-impl-42.md"
|
|
63
67
|
#{program_name} codex --cwd /tmp/public-bundle --id eval-001 --context "Read README.md and write OUTPUT.md" --auto-stop
|
|
68
|
+
#{program_name} pi --id pi-i-52 --artifact-report .harnex/reports/pi-i-52.json --context "Write proof to $HARNEX_ARTIFACT_REPORT_PATH" --auto-stop
|
|
64
69
|
#{program_name} claude --id cl-r-42 --tmux cl-r-42 --description "Review task 42"
|
|
65
70
|
|
|
66
71
|
Gotchas:
|
|
@@ -91,6 +96,7 @@ module Harnex
|
|
|
91
96
|
context: nil,
|
|
92
97
|
meta: nil,
|
|
93
98
|
summary_out: nil,
|
|
99
|
+
artifact_report: nil,
|
|
94
100
|
cwd: nil,
|
|
95
101
|
root: nil,
|
|
96
102
|
auto_stop: false,
|
|
@@ -117,6 +123,7 @@ module Harnex
|
|
|
117
123
|
|
|
118
124
|
repo_root = resolve_run_root(cli_name, child_args)
|
|
119
125
|
@options[:summary_out] = resolve_summary_out(repo_root)
|
|
126
|
+
@options[:artifact_report] = resolve_artifact_report(repo_root)
|
|
120
127
|
@options[:id] ||= Harnex.generate_id(repo_root)
|
|
121
128
|
validate_unique_id!(repo_root)
|
|
122
129
|
effective_child_args = apply_context(apply_codex_service_tier(cli_name, child_args))
|
|
@@ -178,6 +185,7 @@ module Harnex
|
|
|
178
185
|
tmux_cmd << "--auto-stop" if @options[:auto_stop]
|
|
179
186
|
tmux_cmd += ["--meta", JSON.generate(@options[:meta])] if @options[:meta]
|
|
180
187
|
tmux_cmd += ["--summary-out", @options[:summary_out]] if @options[:summary_out]
|
|
188
|
+
tmux_cmd += ["--artifact-report", @options[:artifact_report]] if @options[:artifact_report]
|
|
181
189
|
tmux_cmd += ["--cwd", @options[:cwd]] if @options[:cwd]
|
|
182
190
|
tmux_cmd += ["--root", @options[:root]] if @options[:root]
|
|
183
191
|
tmux_cmd += ["--inbox-ttl", @options[:inbox_ttl].to_s]
|
|
@@ -287,6 +295,7 @@ module Harnex
|
|
|
287
295
|
description: @options[:description],
|
|
288
296
|
meta: @options[:meta],
|
|
289
297
|
summary_out: @options[:summary_out],
|
|
298
|
+
artifact_report_path: @options[:artifact_report],
|
|
290
299
|
inbox_ttl: @options[:inbox_ttl],
|
|
291
300
|
auto_stop: @options[:auto_stop],
|
|
292
301
|
launch_cwd: history_cwd,
|
|
@@ -468,6 +477,13 @@ module Harnex
|
|
|
468
477
|
@options[:summary_out] = required_option_value(arg, argv[index])
|
|
469
478
|
when /\A--summary-out=(.+)\z/
|
|
470
479
|
@options[:summary_out] = required_option_value("--summary-out", Regexp.last_match(1))
|
|
480
|
+
when "--artifact-report", "--validation-report"
|
|
481
|
+
index += 1
|
|
482
|
+
@options[:artifact_report] = required_option_value(arg, argv[index])
|
|
483
|
+
when /\A--artifact-report=(.+)\z/
|
|
484
|
+
@options[:artifact_report] = required_option_value("--artifact-report", Regexp.last_match(1))
|
|
485
|
+
when /\A--validation-report=(.+)\z/
|
|
486
|
+
@options[:artifact_report] = required_option_value("--validation-report", Regexp.last_match(1))
|
|
471
487
|
when "--cwd"
|
|
472
488
|
index += 1
|
|
473
489
|
@options[:cwd] = expand_existing_directory(required_option_value(arg, argv[index]), option_name: arg)
|
|
@@ -546,7 +562,7 @@ module Harnex
|
|
|
546
562
|
nil
|
|
547
563
|
when *VALUE_FLAGS
|
|
548
564
|
index += 1
|
|
549
|
-
when /\A--(?:id|description|host|port|watch|watch-file|stall-after|max-resumes|context|meta|summary-out|cwd|root|timeout|inbox-ttl)=/
|
|
565
|
+
when /\A--(?:id|description|host|port|watch|watch-file|stall-after|max-resumes|context|meta|summary-out|artifact-report|validation-report|cwd|root|timeout|inbox-ttl)=/
|
|
550
566
|
nil
|
|
551
567
|
when /\A--preset=/
|
|
552
568
|
nil
|
|
@@ -565,7 +581,7 @@ module Harnex
|
|
|
565
581
|
arg.start_with?(
|
|
566
582
|
"--id=", "--description=", "--tmux=", "--host=", "--port=", "--watch=", "--watch-file=",
|
|
567
583
|
"--stall-after=", "--max-resumes=", "--preset=", "--context=", "--meta=", "--summary-out=",
|
|
568
|
-
"--cwd=", "--root=", "--timeout=", "--inbox-ttl="
|
|
584
|
+
"--artifact-report=", "--validation-report=", "--cwd=", "--root=", "--timeout=", "--inbox-ttl="
|
|
569
585
|
)
|
|
570
586
|
end
|
|
571
587
|
|
|
@@ -645,6 +661,15 @@ module Harnex
|
|
|
645
661
|
File.expand_path(configured, repo_root)
|
|
646
662
|
end
|
|
647
663
|
|
|
664
|
+
def resolve_artifact_report(repo_root)
|
|
665
|
+
configured = @options[:artifact_report]
|
|
666
|
+
return nil if configured.nil?
|
|
667
|
+
|
|
668
|
+
path = File.expand_path(configured, repo_root)
|
|
669
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
670
|
+
path
|
|
671
|
+
end
|
|
672
|
+
|
|
648
673
|
def default_inbox_ttl
|
|
649
674
|
value = ENV["HARNEX_INBOX_TTL"]
|
|
650
675
|
return Inbox::DEFAULT_TTL.to_f if value.nil? || value.strip.empty?
|
|
@@ -59,10 +59,10 @@ module Harnex
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
attr_reader :repo_root, :launch_cwd, :child_cwd, :host, :port, :session_id, :token, :command, :pid, :id, :adapter, :watch,
|
|
62
|
-
:inbox, :description, :meta, :summary_out, :output_log_path, :events_log_path,
|
|
62
|
+
:inbox, :description, :meta, :summary_out, :artifact_report_path, :output_log_path, :events_log_path,
|
|
63
63
|
:started_at, :ended_at, :exit_code, :term_signal
|
|
64
64
|
|
|
65
|
-
def initialize(adapter:, command:, repo_root:, host:, port: nil, id: DEFAULT_ID, watch: nil, description: nil, meta: nil, summary_out: nil, inbox_ttl: Inbox::DEFAULT_TTL, auto_stop: false, launch_cwd: nil, child_cwd: nil)
|
|
65
|
+
def initialize(adapter:, command:, repo_root:, host:, port: nil, id: DEFAULT_ID, watch: nil, description: nil, meta: nil, summary_out: nil, artifact_report_path: nil, inbox_ttl: Inbox::DEFAULT_TTL, auto_stop: false, launch_cwd: nil, child_cwd: nil)
|
|
66
66
|
@adapter = adapter
|
|
67
67
|
@command = command
|
|
68
68
|
@repo_root = repo_root
|
|
@@ -76,6 +76,9 @@ module Harnex
|
|
|
76
76
|
@meta = meta
|
|
77
77
|
@summary_out = summary_out.to_s.strip
|
|
78
78
|
@summary_out = nil if @summary_out.empty?
|
|
79
|
+
@artifact_report_path = artifact_report_path.to_s.strip
|
|
80
|
+
@artifact_report_path = nil if @artifact_report_path.empty?
|
|
81
|
+
@artifact_report_path = File.expand_path(@artifact_report_path, repo_root) if @artifact_report_path
|
|
79
82
|
@registry_path = Harnex.registry_path(repo_root, @id)
|
|
80
83
|
@output_log_path = Harnex.output_log_path(repo_root, @id)
|
|
81
84
|
@events_log_path = Harnex.events_log_path(repo_root, @id)
|
|
@@ -772,6 +775,11 @@ module Harnex
|
|
|
772
775
|
"HARNEX_SESSION_REPO_ROOT" => repo_root
|
|
773
776
|
}
|
|
774
777
|
env["HARNEX_DESCRIPTION"] = description if description
|
|
778
|
+
if artifact_report_path
|
|
779
|
+
env["HARNEX_ARTIFACT_REPORT_PATH"] = artifact_report_path
|
|
780
|
+
env["HARNEX_VALIDATION_REPORT_PATH"] = artifact_report_path
|
|
781
|
+
env["HARNEX_ARTIFACT_REPORT_SCHEMA"] = Harnex::ArtifactReport::SCHEMA
|
|
782
|
+
end
|
|
775
783
|
env["HARNEX_SPAWNER_PANE"] = ENV["TMUX_PANE"] if ENV["TMUX_PANE"]
|
|
776
784
|
env
|
|
777
785
|
end
|
|
@@ -1157,11 +1165,13 @@ module Harnex
|
|
|
1157
1165
|
end
|
|
1158
1166
|
|
|
1159
1167
|
def build_summary_record
|
|
1160
|
-
{
|
|
1168
|
+
record = {
|
|
1161
1169
|
meta: build_summary_meta,
|
|
1162
1170
|
predicted: summary_predicted_payload,
|
|
1163
1171
|
actual: build_summary_actual
|
|
1164
1172
|
}
|
|
1173
|
+
record.merge!(artifact_report_summary) if artifact_report_path
|
|
1174
|
+
record
|
|
1165
1175
|
end
|
|
1166
1176
|
|
|
1167
1177
|
def build_summary_meta
|
|
@@ -1297,6 +1307,21 @@ module Harnex
|
|
|
1297
1307
|
predicted.is_a?(Hash) ? predicted : {}
|
|
1298
1308
|
end
|
|
1299
1309
|
|
|
1310
|
+
def artifact_report_summary
|
|
1311
|
+
Harnex::ArtifactReport.ingest(artifact_report_path)
|
|
1312
|
+
rescue StandardError => e
|
|
1313
|
+
{
|
|
1314
|
+
"artifact_report" => {
|
|
1315
|
+
"path" => artifact_report_path,
|
|
1316
|
+
"bytes" => nil,
|
|
1317
|
+
"sha256" => nil,
|
|
1318
|
+
"ingest_status" => "error",
|
|
1319
|
+
"schema" => nil,
|
|
1320
|
+
"warning" => "artifact report ingest failed: #{e.message}"
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
end
|
|
1324
|
+
|
|
1300
1325
|
def meta_hash
|
|
1301
1326
|
meta.is_a?(Hash) ? meta : {}
|
|
1302
1327
|
end
|
data/lib/harnex/version.rb
CHANGED
data/lib/harnex.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "harnex/version"
|
|
|
6
6
|
require_relative "harnex/core"
|
|
7
7
|
require_relative "harnex/dispatch_history"
|
|
8
8
|
require_relative "harnex/terminal_status"
|
|
9
|
+
require_relative "harnex/artifact_report"
|
|
9
10
|
require_relative "harnex/watcher"
|
|
10
11
|
require_relative "harnex/adapters"
|
|
11
12
|
require_relative "harnex/runtime/session_state"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: harnex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jikku Jose
|
|
@@ -40,6 +40,7 @@ files:
|
|
|
40
40
|
- lib/harnex/adapters/generic.rb
|
|
41
41
|
- lib/harnex/adapters/opencode.rb
|
|
42
42
|
- lib/harnex/adapters/pi.rb
|
|
43
|
+
- lib/harnex/artifact_report.rb
|
|
43
44
|
- lib/harnex/cli.rb
|
|
44
45
|
- lib/harnex/codex/app_server/client.rb
|
|
45
46
|
- lib/harnex/commands/agents_guide.rb
|