harnex 0.7.9 → 0.7.11
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 +45 -0
- data/README.md +28 -3
- data/guides/01_dispatch.md +30 -0
- data/lib/harnex/artifact_report.rb +221 -0
- data/lib/harnex/commands/run.rb +114 -7
- data/lib/harnex/runtime/session.rb +118 -8
- 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: 104afb9f09fe59bd24191fde1122294a35ae47069a929022228b4365d653da54
|
|
4
|
+
data.tar.gz: ac8032b88f3c0001718ffc053923000abbba33743e6de4b68459108ca481cbcf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f76c4a9847aebd46cca3bada6c0af0e622bb3840fa53160cb4811c2beac2a007c6a01faadf042ef22f73e55c2ddde5571adc56d774e5b444ece412ad656242e4
|
|
7
|
+
data.tar.gz: 1d9843aa631eb411fa31c772cf5c09b92cc5d5d67ed95583abf041fd250e31343c76215a8949f268563e14609416724fd681bad6bfc5c6b7c270a4f9639e6838
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.7.11] - 2026-07-10 | 11:14 PM | IST
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Dispatch summaries now include top-level additive `agent` and `reliability`
|
|
10
|
+
blocks for every run, and a top-level `queue` block when queue attribution is
|
|
11
|
+
provided. New consumers should prefer these blocks over legacy flat fields for
|
|
12
|
+
queue economics and reliability analytics.
|
|
13
|
+
- `harnex run` now accepts first-class queue/agent telemetry flags:
|
|
14
|
+
`--project-id`, `--queue-id`, `--entry-id`, `--entry-title`, `--phase`,
|
|
15
|
+
`--tier`, `--issue`, `--plan`, `--intent`, `--model`, and `--effort`.
|
|
16
|
+
Explicit flags override same-named `--meta` values and are forwarded through
|
|
17
|
+
tmux re-exec.
|
|
18
|
+
- `harnex run --require-attribution` fails before launching unless required
|
|
19
|
+
queue attribution is present (`project_id`, `phase`, `intent`, and at least
|
|
20
|
+
one work id).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Dispatch telemetry docs, README, and the packaged dispatch agents-guide now
|
|
25
|
+
document queue attribution, effective agent routing fields, reliability split,
|
|
26
|
+
and a sample queue grouping snippet.
|
|
27
|
+
|
|
28
|
+
## [0.7.10] - 2026-07-10 | 10:57 PM | IST
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- `harnex run --artifact-report PATH` / `--validation-report PATH` now expose a
|
|
33
|
+
worker-writable `harnex.artifact_report.v1` sidecar path via
|
|
34
|
+
`HARNEX_ARTIFACT_REPORT_PATH` and `HARNEX_VALIDATION_REPORT_PATH`, ingest the
|
|
35
|
+
report at dispatch finalization, and append compact top-level
|
|
36
|
+
`artifact_report`, `validation`, and `artifacts` blocks to the dispatch
|
|
37
|
+
summary row.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Dispatch telemetry docs, README, and the packaged dispatch agents-guide now
|
|
42
|
+
document sidecar proof while keeping plain-text `koder/` artifacts canonical.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Missing, malformed, unsupported-schema, and oversized artifact reports now
|
|
47
|
+
fail soft with `artifact_report.ingest_status` warning telemetry instead of
|
|
48
|
+
crashing or changing the wrapped process exit code.
|
|
49
|
+
|
|
5
50
|
## [0.7.9] - 2026-07-10 | 10:33 PM | IST
|
|
6
51
|
|
|
7
52
|
### Added
|
data/README.md
CHANGED
|
@@ -268,9 +268,34 @@ 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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
+
|
|
287
|
+
Queue runners can pass first-class attribution without hiding it in prose:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
harnex run pi --project-id harnex --queue-id queue-005 --entry-id SP-4 \
|
|
291
|
+
--phase implement --intent queue-work --require-attribution ...
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Soft budget metadata is copied into summary `meta`; queue/agent/reliability
|
|
295
|
+
metadata is copied into top-level `queue`, `agent`, and `reliability` summary
|
|
296
|
+
blocks. Terminal summary `actual` records timing, exit classification, token
|
|
297
|
+
usage when the adapter can capture it, adapter-reported `cost_usd` when
|
|
298
|
+
reliably available, git deltas,
|
|
274
299
|
task-completion state, operational counters (`stalls`, `force_resumes`,
|
|
275
300
|
`disconnections`, `tool_calls`, `commands_executed`), output/event log paths,
|
|
276
301
|
and rough volume measurements such as `lines_changed`, `output_lines`,
|
data/guides/01_dispatch.md
CHANGED
|
@@ -95,6 +95,36 @@ 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
|
+
|
|
114
|
+
Queue runners should pass first-class attribution so dispatch rows can be grouped
|
|
115
|
+
without path/id heuristics:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
harnex run pi --id pi-i-NN --tmux pi-i-NN \
|
|
119
|
+
--project-id harnex --queue-id queue-005 --entry-id SP-4 \
|
|
120
|
+
--phase implement --intent queue-work --require-attribution \
|
|
121
|
+
--context "Read and execute /tmp/task-impl-NN.md"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`--require-attribution` fails before launch unless `project_id`, `phase`,
|
|
125
|
+
`intent`, and at least one work id (`queue_id`, `entry_id`, `issue`, or `plan`)
|
|
126
|
+
are present.
|
|
127
|
+
|
|
98
128
|
Pi runs use structured RPC (`pi --mode rpc`). Pass Pi child flags after `--`
|
|
99
129
|
(e.g. `harnex run pi --context "..." -- --model anthropic/claude-sonnet-4-5 --thinking high`).
|
|
100
130
|
|
|
@@ -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
|
@@ -5,15 +5,33 @@ require "shellwords"
|
|
|
5
5
|
module Harnex
|
|
6
6
|
class Runner
|
|
7
7
|
DEFAULT_TIMEOUT = 5.0
|
|
8
|
+
TELEMETRY_FLAGS = {
|
|
9
|
+
"--project-id" => "project_id",
|
|
10
|
+
"--queue-id" => "queue_id",
|
|
11
|
+
"--entry-id" => "entry_id",
|
|
12
|
+
"--entry-title" => "entry_title",
|
|
13
|
+
"--phase" => "phase",
|
|
14
|
+
"--tier" => "tier",
|
|
15
|
+
"--issue" => "issue",
|
|
16
|
+
"--plan" => "plan",
|
|
17
|
+
"--intent" => "intent",
|
|
18
|
+
"--model" => "model",
|
|
19
|
+
"--effort" => "effort"
|
|
20
|
+
}.freeze
|
|
21
|
+
TELEMETRY_KEYS_TO_FLAGS = TELEMETRY_FLAGS.invert.freeze
|
|
22
|
+
TELEMETRY_EQUALS_PREFIXES = TELEMETRY_FLAGS.keys.map { |flag| "#{flag}=" }.freeze
|
|
23
|
+
|
|
8
24
|
KNOWN_FLAGS = %w[
|
|
9
25
|
--id --description --detach --tmux --host --port --watch --watch-file
|
|
10
26
|
--stall-after --max-resumes --preset --context --meta --summary-out
|
|
11
|
-
--cwd --root --timeout --inbox-ttl
|
|
12
|
-
|
|
27
|
+
--artifact-report --validation-report --cwd --root --timeout --inbox-ttl
|
|
28
|
+
--require-attribution --auto-stop --fast --legacy-pty --help
|
|
29
|
+
].concat(TELEMETRY_FLAGS.keys).freeze
|
|
13
30
|
VALUE_FLAGS = %w[
|
|
14
31
|
--id --description --host --port --watch --watch-file --stall-after
|
|
15
|
-
--max-resumes --preset --context --meta --summary-out --
|
|
16
|
-
|
|
32
|
+
--max-resumes --preset --context --meta --summary-out --artifact-report
|
|
33
|
+
--validation-report --cwd --root --timeout --inbox-ttl
|
|
34
|
+
].concat(TELEMETRY_FLAGS.keys).freeze
|
|
17
35
|
|
|
18
36
|
def self.usage(program_name = "harnex run")
|
|
19
37
|
<<~TEXT
|
|
@@ -37,6 +55,23 @@ module Harnex
|
|
|
37
55
|
Default Codex runs force service_tier="flex".
|
|
38
56
|
--meta JSON Attach parsed JSON metadata to the started event
|
|
39
57
|
--summary-out PATH Append dispatch telemetry summary JSONL to PATH
|
|
58
|
+
--artifact-report PATH
|
|
59
|
+
Worker-written harnex.artifact_report.v1 JSON sidecar to ingest at exit
|
|
60
|
+
--validation-report PATH
|
|
61
|
+
Alias for --artifact-report; also exposed as HARNEX_VALIDATION_REPORT_PATH
|
|
62
|
+
--project-id ID Queue telemetry project id (first-class flags override --meta)
|
|
63
|
+
--queue-id ID Queue telemetry queue id
|
|
64
|
+
--entry-id ID Queue telemetry entry id
|
|
65
|
+
--entry-title TEXT Queue telemetry entry title
|
|
66
|
+
--phase TEXT Queue/work phase telemetry
|
|
67
|
+
--tier TEXT Queue/work tier telemetry
|
|
68
|
+
--issue ID Queue/work issue telemetry
|
|
69
|
+
--plan ID Queue/work plan telemetry
|
|
70
|
+
--intent TEXT Queue/work intent telemetry
|
|
71
|
+
--model NAME Requested model metadata (also used for structured dispatch)
|
|
72
|
+
--effort LEVEL Requested reasoning effort metadata (structured dispatch)
|
|
73
|
+
--require-attribution
|
|
74
|
+
Fail before launch unless project/phase/intent and one work id are present
|
|
40
75
|
--cwd DIR Run the wrapped agent from DIR and use DIR as the session root
|
|
41
76
|
--root DIR Override harnex session/root attribution without changing child cwd
|
|
42
77
|
--timeout SECS Max seconds to wait for detached registration (default: #{DEFAULT_TIMEOUT})
|
|
@@ -61,6 +96,8 @@ module Harnex
|
|
|
61
96
|
#{program_name} pi --id pi-i-42 --tmux pi-i-42 --context "Read /tmp/task-impl-42.md" --auto-stop
|
|
62
97
|
#{program_name} pi --id pi-i-42 --watch --preset impl --context "Read /tmp/task-impl-42.md"
|
|
63
98
|
#{program_name} codex --cwd /tmp/public-bundle --id eval-001 --context "Read README.md and write OUTPUT.md" --auto-stop
|
|
99
|
+
#{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
|
|
100
|
+
#{program_name} pi --project-id harnex --queue-id queue-005 --entry-id SP-4 --phase implement --intent queue-work --require-attribution --context "Implement SP-4"
|
|
64
101
|
#{program_name} claude --id cl-r-42 --tmux cl-r-42 --description "Review task 42"
|
|
65
102
|
|
|
66
103
|
Gotchas:
|
|
@@ -90,7 +127,10 @@ module Harnex
|
|
|
90
127
|
watch: nil,
|
|
91
128
|
context: nil,
|
|
92
129
|
meta: nil,
|
|
130
|
+
telemetry: {},
|
|
131
|
+
require_attribution: false,
|
|
93
132
|
summary_out: nil,
|
|
133
|
+
artifact_report: nil,
|
|
94
134
|
cwd: nil,
|
|
95
135
|
root: nil,
|
|
96
136
|
auto_stop: false,
|
|
@@ -114,9 +154,12 @@ module Harnex
|
|
|
114
154
|
|
|
115
155
|
raise OptionParser::MissingArgument, "cli" if cli_name.nil?
|
|
116
156
|
validate_auto_stop_context!
|
|
157
|
+
apply_telemetry_options!
|
|
158
|
+
validate_required_attribution!
|
|
117
159
|
|
|
118
160
|
repo_root = resolve_run_root(cli_name, child_args)
|
|
119
161
|
@options[:summary_out] = resolve_summary_out(repo_root)
|
|
162
|
+
@options[:artifact_report] = resolve_artifact_report(repo_root)
|
|
120
163
|
@options[:id] ||= Harnex.generate_id(repo_root)
|
|
121
164
|
validate_unique_id!(repo_root)
|
|
122
165
|
effective_child_args = apply_context(apply_codex_service_tier(cli_name, child_args))
|
|
@@ -177,7 +220,13 @@ module Harnex
|
|
|
177
220
|
tmux_cmd += ["--context", @options[:context]] if @options[:context]
|
|
178
221
|
tmux_cmd << "--auto-stop" if @options[:auto_stop]
|
|
179
222
|
tmux_cmd += ["--meta", JSON.generate(@options[:meta])] if @options[:meta]
|
|
223
|
+
@options[:telemetry].each do |key, value|
|
|
224
|
+
flag = TELEMETRY_KEYS_TO_FLAGS[key]
|
|
225
|
+
tmux_cmd += [flag, value] if flag && value
|
|
226
|
+
end
|
|
227
|
+
tmux_cmd << "--require-attribution" if @options[:require_attribution]
|
|
180
228
|
tmux_cmd += ["--summary-out", @options[:summary_out]] if @options[:summary_out]
|
|
229
|
+
tmux_cmd += ["--artifact-report", @options[:artifact_report]] if @options[:artifact_report]
|
|
181
230
|
tmux_cmd += ["--cwd", @options[:cwd]] if @options[:cwd]
|
|
182
231
|
tmux_cmd += ["--root", @options[:root]] if @options[:root]
|
|
183
232
|
tmux_cmd += ["--inbox-ttl", @options[:inbox_ttl].to_s]
|
|
@@ -287,6 +336,7 @@ module Harnex
|
|
|
287
336
|
description: @options[:description],
|
|
288
337
|
meta: @options[:meta],
|
|
289
338
|
summary_out: @options[:summary_out],
|
|
339
|
+
artifact_report_path: @options[:artifact_report],
|
|
290
340
|
inbox_ttl: @options[:inbox_ttl],
|
|
291
341
|
auto_stop: @options[:auto_stop],
|
|
292
342
|
launch_cwd: history_cwd,
|
|
@@ -456,6 +506,8 @@ module Harnex
|
|
|
456
506
|
@options[:context] = required_option_value("--context", Regexp.last_match(1))
|
|
457
507
|
when "--auto-stop"
|
|
458
508
|
@options[:auto_stop] = true
|
|
509
|
+
when "--require-attribution"
|
|
510
|
+
@options[:require_attribution] = true
|
|
459
511
|
when "--fast"
|
|
460
512
|
@options[:fast] = true
|
|
461
513
|
when "--meta"
|
|
@@ -463,11 +515,24 @@ module Harnex
|
|
|
463
515
|
@options[:meta] = parse_meta(required_option_value(arg, argv[index]))
|
|
464
516
|
when /\A--meta=(.+)\z/
|
|
465
517
|
@options[:meta] = parse_meta(required_option_value("--meta", Regexp.last_match(1)))
|
|
518
|
+
when *TELEMETRY_FLAGS.keys
|
|
519
|
+
index += 1
|
|
520
|
+
@options[:telemetry][TELEMETRY_FLAGS.fetch(arg)] = required_option_value(arg, argv[index])
|
|
521
|
+
when telemetry_equals_regex
|
|
522
|
+
flag = "--#{Regexp.last_match(1)}"
|
|
523
|
+
@options[:telemetry][TELEMETRY_FLAGS.fetch(flag)] = required_option_value(flag, Regexp.last_match(2))
|
|
466
524
|
when "--summary-out"
|
|
467
525
|
index += 1
|
|
468
526
|
@options[:summary_out] = required_option_value(arg, argv[index])
|
|
469
527
|
when /\A--summary-out=(.+)\z/
|
|
470
528
|
@options[:summary_out] = required_option_value("--summary-out", Regexp.last_match(1))
|
|
529
|
+
when "--artifact-report", "--validation-report"
|
|
530
|
+
index += 1
|
|
531
|
+
@options[:artifact_report] = required_option_value(arg, argv[index])
|
|
532
|
+
when /\A--artifact-report=(.+)\z/
|
|
533
|
+
@options[:artifact_report] = required_option_value("--artifact-report", Regexp.last_match(1))
|
|
534
|
+
when /\A--validation-report=(.+)\z/
|
|
535
|
+
@options[:artifact_report] = required_option_value("--validation-report", Regexp.last_match(1))
|
|
471
536
|
when "--cwd"
|
|
472
537
|
index += 1
|
|
473
538
|
@options[:cwd] = expand_existing_directory(required_option_value(arg, argv[index]), option_name: arg)
|
|
@@ -504,6 +569,10 @@ module Harnex
|
|
|
504
569
|
[cli_name, forwarded]
|
|
505
570
|
end
|
|
506
571
|
|
|
572
|
+
def telemetry_equals_regex
|
|
573
|
+
@telemetry_equals_regex ||= /\A--(#{TELEMETRY_FLAGS.keys.map { |flag| Regexp.escape(flag.delete_prefix("--")) }.join("|")})=(.+)\z/
|
|
574
|
+
end
|
|
575
|
+
|
|
507
576
|
def unknown_long_flag?(arg)
|
|
508
577
|
arg.start_with?("--")
|
|
509
578
|
end
|
|
@@ -540,13 +609,15 @@ module Harnex
|
|
|
540
609
|
case arg
|
|
541
610
|
when "--"
|
|
542
611
|
return false
|
|
543
|
-
when "-h", "--help", "--detach", "--tmux", "--auto-stop", "--fast", "--legacy-pty"
|
|
612
|
+
when "-h", "--help", "--detach", "--tmux", "--auto-stop", "--require-attribution", "--fast", "--legacy-pty"
|
|
544
613
|
nil
|
|
545
614
|
when /\A--tmux=/
|
|
546
615
|
nil
|
|
547
616
|
when *VALUE_FLAGS
|
|
548
617
|
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)=/
|
|
618
|
+
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)=/
|
|
619
|
+
nil
|
|
620
|
+
when telemetry_equals_regex
|
|
550
621
|
nil
|
|
551
622
|
when /\A--preset=/
|
|
552
623
|
nil
|
|
@@ -565,7 +636,8 @@ module Harnex
|
|
|
565
636
|
arg.start_with?(
|
|
566
637
|
"--id=", "--description=", "--tmux=", "--host=", "--port=", "--watch=", "--watch-file=",
|
|
567
638
|
"--stall-after=", "--max-resumes=", "--preset=", "--context=", "--meta=", "--summary-out=",
|
|
568
|
-
"--cwd=", "--root=", "--timeout=", "--inbox-ttl="
|
|
639
|
+
"--artifact-report=", "--validation-report=", "--cwd=", "--root=", "--timeout=", "--inbox-ttl=",
|
|
640
|
+
*TELEMETRY_EQUALS_PREFIXES
|
|
569
641
|
)
|
|
570
642
|
end
|
|
571
643
|
|
|
@@ -594,6 +666,32 @@ module Harnex
|
|
|
594
666
|
raise OptionParser::InvalidOption, "harnex run: --auto-stop requires --context"
|
|
595
667
|
end
|
|
596
668
|
|
|
669
|
+
def apply_telemetry_options!
|
|
670
|
+
explicit = @options[:telemetry]
|
|
671
|
+
return if explicit.empty? && @options[:meta].is_a?(Hash)
|
|
672
|
+
return if explicit.empty?
|
|
673
|
+
|
|
674
|
+
@options[:meta] = (@options[:meta].is_a?(Hash) ? @options[:meta].dup : {}).merge(explicit)
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def validate_required_attribution!
|
|
678
|
+
return unless @options[:require_attribution]
|
|
679
|
+
|
|
680
|
+
metadata = @options[:meta].is_a?(Hash) ? @options[:meta] : {}
|
|
681
|
+
missing = %w[project_id phase intent].select { |key| blank_value?(metadata[key]) }
|
|
682
|
+
unless %w[queue_id entry_id issue plan].any? { |key| !blank_value?(metadata[key]) }
|
|
683
|
+
missing << "one of queue_id/entry_id/issue/plan"
|
|
684
|
+
end
|
|
685
|
+
return if missing.empty?
|
|
686
|
+
|
|
687
|
+
raise OptionParser::InvalidOption,
|
|
688
|
+
"harnex run: --require-attribution missing #{missing.join(', ')}"
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
def blank_value?(value)
|
|
692
|
+
value.nil? || value.to_s.strip.empty?
|
|
693
|
+
end
|
|
694
|
+
|
|
597
695
|
def apply_codex_service_tier(cli_name, child_args)
|
|
598
696
|
return child_args unless cli_name.to_s == "codex"
|
|
599
697
|
return child_args if child_service_tier_config?(child_args)
|
|
@@ -645,6 +743,15 @@ module Harnex
|
|
|
645
743
|
File.expand_path(configured, repo_root)
|
|
646
744
|
end
|
|
647
745
|
|
|
746
|
+
def resolve_artifact_report(repo_root)
|
|
747
|
+
configured = @options[:artifact_report]
|
|
748
|
+
return nil if configured.nil?
|
|
749
|
+
|
|
750
|
+
path = File.expand_path(configured, repo_root)
|
|
751
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
752
|
+
path
|
|
753
|
+
end
|
|
754
|
+
|
|
648
755
|
def default_inbox_ttl
|
|
649
756
|
value = ENV["HARNEX_INBOX_TTL"]
|
|
650
757
|
return Inbox::DEFAULT_TTL.to_f if value.nil? || value.strip.empty?
|
|
@@ -16,6 +16,11 @@ module Harnex
|
|
|
16
16
|
agent_session_id cost_usd
|
|
17
17
|
].freeze
|
|
18
18
|
BUDGET_META_FIELDS = %w[read_budget_lines output_ceiling_lines].freeze
|
|
19
|
+
QUEUE_FIELDS = %w[project_id queue_id entry_id entry_title issue plan phase tier intent].freeze
|
|
20
|
+
AGENT_FIELDS = %w[cli provider model_requested model_effective reasoning_effort service_tier adapter_transport].freeze
|
|
21
|
+
RELIABILITY_FIELDS = %w[
|
|
22
|
+
adapter_close real_disconnections stream_interruptions stalls force_resumes compactions recovered
|
|
23
|
+
].freeze
|
|
19
24
|
SUCCESSFUL_TURN_STATUSES = %w[completed success succeeded].freeze
|
|
20
25
|
class EventCounters
|
|
21
26
|
def initialize
|
|
@@ -59,10 +64,10 @@ module Harnex
|
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
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,
|
|
67
|
+
:inbox, :description, :meta, :summary_out, :artifact_report_path, :output_log_path, :events_log_path,
|
|
63
68
|
:started_at, :ended_at, :exit_code, :term_signal
|
|
64
69
|
|
|
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)
|
|
70
|
+
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
71
|
@adapter = adapter
|
|
67
72
|
@command = command
|
|
68
73
|
@repo_root = repo_root
|
|
@@ -76,6 +81,9 @@ module Harnex
|
|
|
76
81
|
@meta = meta
|
|
77
82
|
@summary_out = summary_out.to_s.strip
|
|
78
83
|
@summary_out = nil if @summary_out.empty?
|
|
84
|
+
@artifact_report_path = artifact_report_path.to_s.strip
|
|
85
|
+
@artifact_report_path = nil if @artifact_report_path.empty?
|
|
86
|
+
@artifact_report_path = File.expand_path(@artifact_report_path, repo_root) if @artifact_report_path
|
|
79
87
|
@registry_path = Harnex.registry_path(repo_root, @id)
|
|
80
88
|
@output_log_path = Harnex.output_log_path(repo_root, @id)
|
|
81
89
|
@events_log_path = Harnex.events_log_path(repo_root, @id)
|
|
@@ -772,6 +780,11 @@ module Harnex
|
|
|
772
780
|
"HARNEX_SESSION_REPO_ROOT" => repo_root
|
|
773
781
|
}
|
|
774
782
|
env["HARNEX_DESCRIPTION"] = description if description
|
|
783
|
+
if artifact_report_path
|
|
784
|
+
env["HARNEX_ARTIFACT_REPORT_PATH"] = artifact_report_path
|
|
785
|
+
env["HARNEX_VALIDATION_REPORT_PATH"] = artifact_report_path
|
|
786
|
+
env["HARNEX_ARTIFACT_REPORT_SCHEMA"] = Harnex::ArtifactReport::SCHEMA
|
|
787
|
+
end
|
|
775
788
|
env["HARNEX_SPAWNER_PANE"] = ENV["TMUX_PANE"] if ENV["TMUX_PANE"]
|
|
776
789
|
env
|
|
777
790
|
end
|
|
@@ -1157,11 +1170,17 @@ module Harnex
|
|
|
1157
1170
|
end
|
|
1158
1171
|
|
|
1159
1172
|
def build_summary_record
|
|
1160
|
-
{
|
|
1173
|
+
record = {
|
|
1161
1174
|
meta: build_summary_meta,
|
|
1162
1175
|
predicted: summary_predicted_payload,
|
|
1163
|
-
actual: build_summary_actual
|
|
1176
|
+
actual: build_summary_actual,
|
|
1177
|
+
agent: build_summary_agent,
|
|
1178
|
+
reliability: build_summary_reliability
|
|
1164
1179
|
}
|
|
1180
|
+
queue = build_summary_queue
|
|
1181
|
+
record[:queue] = queue if queue
|
|
1182
|
+
record.merge!(artifact_report_summary) if artifact_report_path
|
|
1183
|
+
record
|
|
1165
1184
|
end
|
|
1166
1185
|
|
|
1167
1186
|
def build_summary_meta
|
|
@@ -1197,12 +1216,42 @@ module Harnex
|
|
|
1197
1216
|
}.merge(summary_budget_meta)
|
|
1198
1217
|
end
|
|
1199
1218
|
|
|
1219
|
+
def build_summary_queue
|
|
1220
|
+
queue = QUEUE_FIELDS.to_h { |field| [field, summary_string(meta_hash[field])] }
|
|
1221
|
+
return nil if queue.values.all?(&:nil?)
|
|
1222
|
+
|
|
1223
|
+
queue
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
def build_summary_agent
|
|
1227
|
+
{
|
|
1228
|
+
"cli" => adapter.key,
|
|
1229
|
+
"provider" => summary_agent_provider,
|
|
1230
|
+
"model_requested" => summary_string(meta_hash["model"]),
|
|
1231
|
+
"model_effective" => summary_string(summary_model),
|
|
1232
|
+
"reasoning_effort" => summary_string(meta_hash["effort"]),
|
|
1233
|
+
"service_tier" => summary_service_tier,
|
|
1234
|
+
"adapter_transport" => adapter.transport.to_s
|
|
1235
|
+
}
|
|
1236
|
+
end
|
|
1237
|
+
|
|
1238
|
+
def build_summary_reliability
|
|
1239
|
+
counters = summary_event_counters
|
|
1240
|
+
real_disconnections = counters[:disconnections].to_i
|
|
1241
|
+
{
|
|
1242
|
+
"adapter_close" => summary_adapter_close(real_disconnections),
|
|
1243
|
+
"real_disconnections" => real_disconnections,
|
|
1244
|
+
"stream_interruptions" => real_disconnections,
|
|
1245
|
+
"stalls" => counters[:stalls].to_i,
|
|
1246
|
+
"force_resumes" => counters[:force_resumes].to_i,
|
|
1247
|
+
"compactions" => counters[:compactions].to_i,
|
|
1248
|
+
"recovered" => false
|
|
1249
|
+
}
|
|
1250
|
+
end
|
|
1251
|
+
|
|
1200
1252
|
def build_summary_actual
|
|
1201
|
-
counters =
|
|
1253
|
+
counters = summary_event_counters
|
|
1202
1254
|
output_measurements = summary_output_measurements
|
|
1203
|
-
if %w[disconnected boot_failure].include?(@exit_reason)
|
|
1204
|
-
counters[:disconnections] = [counters[:disconnections], 1].max
|
|
1205
|
-
end
|
|
1206
1255
|
|
|
1207
1256
|
actual = {
|
|
1208
1257
|
model: summary_model,
|
|
@@ -1288,6 +1337,52 @@ module Harnex
|
|
|
1288
1337
|
(adapter.current_model if adapter.respond_to?(:current_model))
|
|
1289
1338
|
end
|
|
1290
1339
|
|
|
1340
|
+
def summary_service_tier
|
|
1341
|
+
summary_string(meta_hash["service_tier"]) || service_tier_from_command
|
|
1342
|
+
end
|
|
1343
|
+
|
|
1344
|
+
def service_tier_from_command
|
|
1345
|
+
command.each_with_index do |arg, index|
|
|
1346
|
+
text = arg.to_s
|
|
1347
|
+
if text == "-c" || text == "--config"
|
|
1348
|
+
parsed = parse_service_tier_config(command[index + 1])
|
|
1349
|
+
return parsed if parsed
|
|
1350
|
+
end
|
|
1351
|
+
parsed = parse_service_tier_config(text)
|
|
1352
|
+
return parsed if parsed
|
|
1353
|
+
end
|
|
1354
|
+
nil
|
|
1355
|
+
end
|
|
1356
|
+
|
|
1357
|
+
def parse_service_tier_config(value)
|
|
1358
|
+
text = value.to_s
|
|
1359
|
+
match = text.match(/(?:\A|=)service_tier=\\?"?([^"\s]+)\\?"?/)
|
|
1360
|
+
return nil unless match
|
|
1361
|
+
|
|
1362
|
+
summary_string(match[1])
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
def summary_event_counters
|
|
1366
|
+
counters = @event_counters.snapshot
|
|
1367
|
+
if %w[disconnected boot_failure].include?(@exit_reason)
|
|
1368
|
+
counters[:disconnections] = [counters[:disconnections], 1].max
|
|
1369
|
+
end
|
|
1370
|
+
counters
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
def summary_adapter_close(real_disconnections)
|
|
1374
|
+
return "interrupted" if @exit_code == 124 || @term_signal
|
|
1375
|
+
return "lost" if %w[disconnected boot_failure].include?(@exit_reason) || real_disconnections.to_i.positive?
|
|
1376
|
+
return "normal" if %w[success failure].include?(@exit_reason)
|
|
1377
|
+
|
|
1378
|
+
"unknown"
|
|
1379
|
+
end
|
|
1380
|
+
|
|
1381
|
+
def summary_string(value)
|
|
1382
|
+
text = value.to_s
|
|
1383
|
+
text.empty? ? nil : text
|
|
1384
|
+
end
|
|
1385
|
+
|
|
1291
1386
|
def summary_tool_calls(counters)
|
|
1292
1387
|
@usage_summary[:tool_calls] || counters[:tool_calls]
|
|
1293
1388
|
end
|
|
@@ -1297,6 +1392,21 @@ module Harnex
|
|
|
1297
1392
|
predicted.is_a?(Hash) ? predicted : {}
|
|
1298
1393
|
end
|
|
1299
1394
|
|
|
1395
|
+
def artifact_report_summary
|
|
1396
|
+
Harnex::ArtifactReport.ingest(artifact_report_path)
|
|
1397
|
+
rescue StandardError => e
|
|
1398
|
+
{
|
|
1399
|
+
"artifact_report" => {
|
|
1400
|
+
"path" => artifact_report_path,
|
|
1401
|
+
"bytes" => nil,
|
|
1402
|
+
"sha256" => nil,
|
|
1403
|
+
"ingest_status" => "error",
|
|
1404
|
+
"schema" => nil,
|
|
1405
|
+
"warning" => "artifact report ingest failed: #{e.message}"
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1300
1410
|
def meta_hash
|
|
1301
1411
|
meta.is_a?(Hash) ? meta : {}
|
|
1302
1412
|
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.11
|
|
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
|