harnex 0.7.13 → 0.7.14
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 +38 -0
- data/README.md +53 -11
- data/TECHNICAL.md +20 -1
- data/guides/01_dispatch.md +29 -9
- data/guides/04_monitoring.md +11 -5
- data/lib/harnex/artifact_report.rb +507 -35
- data/lib/harnex/cli.rb +17 -0
- data/lib/harnex/commands/artifact_report.rb +94 -0
- data/lib/harnex/commands/orchestration.rb +170 -0
- data/lib/harnex/commands/run.rb +49 -5
- data/lib/harnex/commands/wait.rb +6 -1
- data/lib/harnex/commands/watch.rb +2 -0
- data/lib/harnex/orchestration.rb +458 -0
- data/lib/harnex/runtime/session.rb +278 -13
- data/lib/harnex/terminal_status.rb +7 -0
- data/lib/harnex/version.rb +1 -1
- data/lib/harnex.rb +3 -0
- metadata +4 -1
|
@@ -31,6 +31,9 @@ module Harnex
|
|
|
31
31
|
BUDGET_META_FIELDS = %w[read_budget_lines output_ceiling_lines].freeze
|
|
32
32
|
QUEUE_FIELDS = %w[project_id queue_id entry_id entry_title issue plan phase tier intent].freeze
|
|
33
33
|
AGENT_FIELDS = %w[cli provider model_requested model_effective reasoning_effort service_tier adapter_transport].freeze
|
|
34
|
+
ORCHESTRATION_FIELDS = %w[
|
|
35
|
+
run_id generation_id role project_id queue_id session_id rotation_reason
|
|
36
|
+
].freeze
|
|
34
37
|
RELIABILITY_FIELDS = %w[
|
|
35
38
|
adapter_close real_disconnections stream_interruptions stalls force_resumes compactions recovered
|
|
36
39
|
].freeze
|
|
@@ -70,7 +73,7 @@ module Harnex
|
|
|
70
73
|
return unless item.is_a?(Hash)
|
|
71
74
|
|
|
72
75
|
case item["type"]
|
|
73
|
-
when "mcpToolCall", "dynamicToolCall"
|
|
76
|
+
when "mcpToolCall", "dynamicToolCall", "fileChange", "webSearch"
|
|
74
77
|
@counts[:tool_calls] += 1
|
|
75
78
|
when "commandExecution"
|
|
76
79
|
@counts[:commands_executed] += 1
|
|
@@ -84,9 +87,9 @@ module Harnex
|
|
|
84
87
|
|
|
85
88
|
attr_reader :repo_root, :launch_cwd, :child_cwd, :host, :port, :session_id, :token, :command, :pid, :id, :adapter, :watch,
|
|
86
89
|
:inbox, :description, :meta, :summary_out, :artifact_report_path, :output_log_path, :events_log_path,
|
|
87
|
-
:started_at, :ended_at, :exit_code, :term_signal
|
|
90
|
+
:started_at, :ended_at, :exit_code, :term_signal, :require_artifact_report
|
|
88
91
|
|
|
89
|
-
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)
|
|
92
|
+
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, require_artifact_report: false, inbox_ttl: Inbox::DEFAULT_TTL, auto_stop: false, launch_cwd: nil, child_cwd: nil)
|
|
90
93
|
@adapter = adapter
|
|
91
94
|
@command = command
|
|
92
95
|
@repo_root = repo_root
|
|
@@ -103,6 +106,9 @@ module Harnex
|
|
|
103
106
|
@artifact_report_path = artifact_report_path.to_s.strip
|
|
104
107
|
@artifact_report_path = nil if @artifact_report_path.empty?
|
|
105
108
|
@artifact_report_path = File.expand_path(@artifact_report_path, repo_root) if @artifact_report_path
|
|
109
|
+
@require_artifact_report = !!require_artifact_report
|
|
110
|
+
raise ArgumentError, "require_artifact_report requires artifact_report_path" if @require_artifact_report && !@artifact_report_path
|
|
111
|
+
@artifact_report_start_fingerprint = Harnex::ArtifactReport.fingerprint(@artifact_report_path) if @artifact_report_path
|
|
106
112
|
@registry_path = Harnex.registry_path(repo_root, @id)
|
|
107
113
|
@output_log_path = Harnex.output_log_path(repo_root, @id)
|
|
108
114
|
@events_log_path = Harnex.events_log_path(repo_root, @id)
|
|
@@ -136,6 +142,9 @@ module Harnex
|
|
|
136
142
|
@last_completed_at = nil
|
|
137
143
|
@last_failed_at = nil
|
|
138
144
|
@last_failed_status = nil
|
|
145
|
+
@completion_outcome_class = nil
|
|
146
|
+
@completion_report_status = nil
|
|
147
|
+
@completion_diagnostics = []
|
|
139
148
|
@pi_streamed_text_by_message = {}
|
|
140
149
|
@auto_stop = !!auto_stop
|
|
141
150
|
@auto_stop_fired = false
|
|
@@ -208,6 +217,8 @@ module Harnex
|
|
|
208
217
|
@exit_code = status.exited? ? status.exitstatus : 128 + status.termsig
|
|
209
218
|
@ended_at = Time.now
|
|
210
219
|
|
|
220
|
+
enforce_required_artifact_report!
|
|
221
|
+
normalize_work_acceptance_exit_code!
|
|
211
222
|
normalize_auto_stop_exit_code!
|
|
212
223
|
drain_auto_stop_threads
|
|
213
224
|
output_thread.join(1)
|
|
@@ -268,6 +279,8 @@ module Harnex
|
|
|
268
279
|
payload[:task_failed] = task_failed
|
|
269
280
|
payload[:done] = Harnex.work_done_for("running", task_complete: task_complete)
|
|
270
281
|
payload[:work_state] = work_state
|
|
282
|
+
payload[:outcome_class] = @completion_outcome_class
|
|
283
|
+
payload[:artifact_report_status] = @completion_report_status
|
|
271
284
|
payload[:last_error] = @last_error
|
|
272
285
|
payload[:model] = summary_model
|
|
273
286
|
payload[:effort] = meta_hash["effort"]
|
|
@@ -482,6 +495,8 @@ module Harnex
|
|
|
482
495
|
end
|
|
483
496
|
@ended_at = Time.now
|
|
484
497
|
|
|
498
|
+
enforce_required_artifact_report!
|
|
499
|
+
normalize_work_acceptance_exit_code!
|
|
485
500
|
normalize_auto_stop_exit_code!
|
|
486
501
|
drain_auto_stop_threads
|
|
487
502
|
finalize_session!
|
|
@@ -540,8 +555,7 @@ module Harnex
|
|
|
540
555
|
payload[:status] = status if status
|
|
541
556
|
payload[:tokenUsage] = params["tokenUsage"] if params["tokenUsage"].is_a?(Hash)
|
|
542
557
|
if successful_turn_status?(status)
|
|
543
|
-
|
|
544
|
-
emit_event("task_complete", **payload)
|
|
558
|
+
record_successful_completion(payload)
|
|
545
559
|
else
|
|
546
560
|
mark_task_failed(
|
|
547
561
|
turn_id: turn_id,
|
|
@@ -600,15 +614,170 @@ module Harnex
|
|
|
600
614
|
SUCCESSFUL_TURN_STATUSES.include?(text)
|
|
601
615
|
end
|
|
602
616
|
|
|
603
|
-
def
|
|
617
|
+
def record_successful_completion(payload)
|
|
618
|
+
assessment = completion_gate_required? ? assess_completion_proof : { accepted: true }
|
|
619
|
+
unless assessment[:accepted]
|
|
620
|
+
mark_task_failed(
|
|
621
|
+
turn_id: payload[:turnId],
|
|
622
|
+
status: assessment.fetch(:outcome_class),
|
|
623
|
+
error: completion_failure_message(assessment.fetch(:outcome_class)),
|
|
624
|
+
outcome_class: assessment.fetch(:outcome_class),
|
|
625
|
+
artifact_report_status: assessment[:report_status],
|
|
626
|
+
diagnostics: assessment[:diagnostics]
|
|
627
|
+
)
|
|
628
|
+
return false
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
@last_failed_at = nil
|
|
632
|
+
@last_failed_status = nil
|
|
633
|
+
@last_error = nil
|
|
634
|
+
@last_completed_at = Time.now
|
|
635
|
+
if assessment[:outcome_class]
|
|
636
|
+
@completion_outcome_class = assessment[:outcome_class]
|
|
637
|
+
@completion_report_status = assessment[:report_status]
|
|
638
|
+
@completion_diagnostics = []
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
event_payload = payload.dup
|
|
642
|
+
event_payload[:outcome_class] = @completion_outcome_class if @completion_outcome_class
|
|
643
|
+
event_payload[:artifact_report_status] = @completion_report_status if @completion_report_status
|
|
644
|
+
emit_event("task_complete", **event_payload)
|
|
645
|
+
true
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def completion_gate_required?
|
|
649
|
+
return true if require_artifact_report
|
|
650
|
+
return false unless adapter.transport == :stdio_jsonrpc
|
|
651
|
+
|
|
652
|
+
@auto_stop || (adapter.respond_to?(:initial_prompt) && !adapter.initial_prompt.to_s.empty?)
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def assess_completion_proof
|
|
656
|
+
report_result = artifact_report_path ? Harnex::ArtifactReport.validate(artifact_report_path, final: true) : nil
|
|
657
|
+
report_fresh = report_result && report_result.status != "missing" && artifact_report_fresh?
|
|
658
|
+
report_stale = report_result && Harnex::ArtifactReport.accepted_final?(report_result) && !report_fresh
|
|
659
|
+
report_status = report_stale ? "stale" : report_result&.status
|
|
660
|
+
report_diagnostics = if report_stale
|
|
661
|
+
[artifact_report_stale_diagnostic]
|
|
662
|
+
else
|
|
663
|
+
report_result&.diagnostics || []
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
if report_result
|
|
667
|
+
if report_fresh && Harnex::ArtifactReport.accepted_final?(report_result)
|
|
668
|
+
return {
|
|
669
|
+
accepted: true,
|
|
670
|
+
outcome_class: "completed_with_proof",
|
|
671
|
+
report_status: "accepted",
|
|
672
|
+
diagnostics: []
|
|
673
|
+
}
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
report_outcome = Harnex::ArtifactReport.outcome_status(report_result)
|
|
677
|
+
if report_fresh && report_outcome == "rejected"
|
|
678
|
+
return {
|
|
679
|
+
accepted: false,
|
|
680
|
+
outcome_class: "report_rejected",
|
|
681
|
+
report_status: "rejected",
|
|
682
|
+
diagnostics: report_result.diagnostics
|
|
683
|
+
}
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
if require_artifact_report
|
|
687
|
+
outcome_class = report_result.status == "missing" ? "report_missing" : "report_invalid"
|
|
688
|
+
return {
|
|
689
|
+
accepted: false,
|
|
690
|
+
outcome_class: outcome_class,
|
|
691
|
+
report_status: report_status,
|
|
692
|
+
diagnostics: report_diagnostics
|
|
693
|
+
}
|
|
694
|
+
end
|
|
695
|
+
elsif require_artifact_report
|
|
696
|
+
return {
|
|
697
|
+
accepted: false,
|
|
698
|
+
outcome_class: "report_missing",
|
|
699
|
+
report_status: "missing",
|
|
700
|
+
diagnostics: []
|
|
701
|
+
}
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
if structured_activity_observed? || git_activity_observed?
|
|
705
|
+
{
|
|
706
|
+
accepted: true,
|
|
707
|
+
outcome_class: "completed_with_activity",
|
|
708
|
+
report_status: report_status,
|
|
709
|
+
diagnostics: []
|
|
710
|
+
}
|
|
711
|
+
else
|
|
712
|
+
{
|
|
713
|
+
accepted: false,
|
|
714
|
+
outcome_class: "completed_no_activity",
|
|
715
|
+
report_status: report_status,
|
|
716
|
+
diagnostics: report_diagnostics
|
|
717
|
+
}
|
|
718
|
+
end
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
def artifact_report_fresh?
|
|
722
|
+
current = Harnex::ArtifactReport.fingerprint(artifact_report_path)
|
|
723
|
+
return false unless current
|
|
724
|
+
return true unless @artifact_report_start_fingerprint
|
|
725
|
+
|
|
726
|
+
current != @artifact_report_start_fingerprint
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
def artifact_report_stale_diagnostic
|
|
730
|
+
{
|
|
731
|
+
"code" => "report_stale",
|
|
732
|
+
"path" => "$",
|
|
733
|
+
"message" => "artifact report was not created or updated during this session"
|
|
734
|
+
}
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
def structured_activity_observed?
|
|
738
|
+
counters = @event_counters.snapshot
|
|
739
|
+
counters[:commands_executed].to_i.positive? || counters[:tool_calls].to_i.positive?
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
def git_activity_observed?
|
|
743
|
+
return false if @git_start[:sha].to_s.empty?
|
|
744
|
+
|
|
745
|
+
snapshot = Harnex.git_capture_end(repo_root, @git_start[:sha])
|
|
746
|
+
snapshot[:commits].to_i.positive? || Array(snapshot[:changed_paths]).any? ||
|
|
747
|
+
(!snapshot[:sha].to_s.empty? && snapshot[:sha].to_s != @git_start[:sha].to_s)
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def completion_failure_message(outcome_class)
|
|
751
|
+
case outcome_class
|
|
752
|
+
when "completed_no_activity"
|
|
753
|
+
"turn completed without command/tool execution, Git delta, or accepted artifact report"
|
|
754
|
+
when "report_missing"
|
|
755
|
+
"required artifact report is missing"
|
|
756
|
+
when "report_rejected"
|
|
757
|
+
"artifact report rejected work completion"
|
|
758
|
+
when "report_invalid"
|
|
759
|
+
"required artifact report is not valid final proof"
|
|
760
|
+
else
|
|
761
|
+
"work completion was not accepted"
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
def mark_task_failed(turn_id: nil, status: nil, error: nil, codex_error_info: nil, outcome_class: nil, artifact_report_status: nil, diagnostics: nil)
|
|
766
|
+
@last_completed_at = nil if outcome_class
|
|
604
767
|
@last_failed_at = Time.now
|
|
605
768
|
@last_failed_status = status.to_s.empty? ? "failed" : status.to_s
|
|
606
769
|
@last_error = error.to_s unless error.to_s.empty?
|
|
770
|
+
@completion_outcome_class = outcome_class if outcome_class
|
|
771
|
+
@completion_report_status = artifact_report_status if artifact_report_status
|
|
772
|
+
@completion_diagnostics = Array(diagnostics).first(Harnex::ArtifactReport::MAX_DIAGNOSTICS) if diagnostics
|
|
607
773
|
|
|
608
774
|
payload = { status: @last_failed_status }
|
|
609
775
|
payload[:turnId] = turn_id if turn_id
|
|
610
776
|
payload[:message] = error unless error.to_s.empty?
|
|
611
777
|
payload[:codex_error_info] = codex_error_info if codex_error_info
|
|
778
|
+
payload[:outcome_class] = outcome_class if outcome_class
|
|
779
|
+
payload[:artifact_report_status] = artifact_report_status if artifact_report_status
|
|
780
|
+
payload[:diagnostics] = @completion_diagnostics unless @completion_diagnostics.empty?
|
|
612
781
|
emit_event("task_failed", **payload)
|
|
613
782
|
end
|
|
614
783
|
|
|
@@ -655,9 +824,8 @@ module Harnex
|
|
|
655
824
|
@state_machine.force_busy!
|
|
656
825
|
emit_event("turn_started") if event_type == "turn_start"
|
|
657
826
|
when "agent_end"
|
|
658
|
-
@last_completed_at = Time.now
|
|
659
827
|
@state_machine.force_prompt!
|
|
660
|
-
|
|
828
|
+
record_successful_completion({})
|
|
661
829
|
adapter.request_session_stats_async if adapter.respond_to?(:request_session_stats_async)
|
|
662
830
|
schedule_auto_stop("task_complete", interrupt: false)
|
|
663
831
|
when "message_start"
|
|
@@ -836,6 +1004,7 @@ module Harnex
|
|
|
836
1004
|
env["HARNEX_ARTIFACT_REPORT_PATH"] = artifact_report_path
|
|
837
1005
|
env["HARNEX_VALIDATION_REPORT_PATH"] = artifact_report_path
|
|
838
1006
|
env["HARNEX_ARTIFACT_REPORT_SCHEMA"] = Harnex::ArtifactReport::SCHEMA
|
|
1007
|
+
env["HARNEX_ARTIFACT_REPORT_REQUIRED"] = "1" if require_artifact_report
|
|
839
1008
|
end
|
|
840
1009
|
env["HARNEX_SPAWNER_PANE"] = ENV["TMUX_PANE"] if ENV["TMUX_PANE"]
|
|
841
1010
|
env
|
|
@@ -920,6 +1089,8 @@ module Harnex
|
|
|
920
1089
|
task_failed: task_failed,
|
|
921
1090
|
done: Harnex.work_done_for(state, task_complete: task_complete),
|
|
922
1091
|
work_state: Harnex.work_state_for(state, task_complete: task_complete),
|
|
1092
|
+
outcome_class: @completion_outcome_class,
|
|
1093
|
+
artifact_report_status: @completion_report_status,
|
|
923
1094
|
started_at: @started_at.iso8601,
|
|
924
1095
|
exited_at: Time.now.iso8601,
|
|
925
1096
|
injected_count: @injected_count
|
|
@@ -1143,7 +1314,10 @@ module Harnex
|
|
|
1143
1314
|
seen_busy = @auto_stop_mutex.synchronize do
|
|
1144
1315
|
@auto_stop_seen_busy ||= old_state == :busy || new_state == :busy
|
|
1145
1316
|
end
|
|
1146
|
-
|
|
1317
|
+
return unless seen_busy && new_state == :prompt
|
|
1318
|
+
|
|
1319
|
+
record_successful_completion({}) if require_artifact_report
|
|
1320
|
+
schedule_auto_stop("prompt_after_busy")
|
|
1147
1321
|
end
|
|
1148
1322
|
|
|
1149
1323
|
def schedule_auto_stop(reason, turn_id: nil, interrupt: true)
|
|
@@ -1205,6 +1379,55 @@ module Harnex
|
|
|
1205
1379
|
AUTOSTOP_TEARDOWN_GRACE_SECONDS_DEFAULT
|
|
1206
1380
|
end
|
|
1207
1381
|
|
|
1382
|
+
def enforce_required_artifact_report!
|
|
1383
|
+
return unless require_artifact_report
|
|
1384
|
+
return if task_failed? && %w[report_missing report_invalid report_rejected].include?(@completion_outcome_class)
|
|
1385
|
+
|
|
1386
|
+
result = Harnex::ArtifactReport.validate(artifact_report_path, final: true)
|
|
1387
|
+
report_fresh = result.status != "missing" && artifact_report_fresh?
|
|
1388
|
+
report_accepted = Harnex::ArtifactReport.accepted_final?(result)
|
|
1389
|
+
report_stale = report_accepted && !report_fresh
|
|
1390
|
+
if report_fresh && report_accepted
|
|
1391
|
+
@completion_outcome_class = "completed_with_proof"
|
|
1392
|
+
@completion_report_status = "accepted"
|
|
1393
|
+
@completion_diagnostics = []
|
|
1394
|
+
return
|
|
1395
|
+
end
|
|
1396
|
+
|
|
1397
|
+
report_outcome = Harnex::ArtifactReport.outcome_status(result)
|
|
1398
|
+
outcome_class = if report_fresh && report_outcome == "rejected"
|
|
1399
|
+
"report_rejected"
|
|
1400
|
+
elsif result.status == "missing"
|
|
1401
|
+
"report_missing"
|
|
1402
|
+
else
|
|
1403
|
+
"report_invalid"
|
|
1404
|
+
end
|
|
1405
|
+
report_status = if report_stale
|
|
1406
|
+
"stale"
|
|
1407
|
+
elsif report_fresh && report_outcome == "rejected"
|
|
1408
|
+
"rejected"
|
|
1409
|
+
else
|
|
1410
|
+
result.status
|
|
1411
|
+
end
|
|
1412
|
+
diagnostics = report_stale ? [artifact_report_stale_diagnostic] : result.diagnostics
|
|
1413
|
+
mark_task_failed(
|
|
1414
|
+
status: outcome_class,
|
|
1415
|
+
error: completion_failure_message(outcome_class),
|
|
1416
|
+
outcome_class: outcome_class,
|
|
1417
|
+
artifact_report_status: report_status,
|
|
1418
|
+
diagnostics: diagnostics
|
|
1419
|
+
)
|
|
1420
|
+
@exit_code = 1 if @exit_code.nil? || @exit_code.zero? || @term_signal
|
|
1421
|
+
@term_signal = nil if @exit_code == 1
|
|
1422
|
+
end
|
|
1423
|
+
|
|
1424
|
+
def normalize_work_acceptance_exit_code!
|
|
1425
|
+
return unless task_failed? && @completion_outcome_class
|
|
1426
|
+
|
|
1427
|
+
@exit_code = 1 if @exit_code.nil? || @exit_code.zero? || @term_signal
|
|
1428
|
+
@term_signal = nil if @exit_code == 1
|
|
1429
|
+
end
|
|
1430
|
+
|
|
1208
1431
|
def normalize_auto_stop_exit_code!
|
|
1209
1432
|
return unless @auto_stop
|
|
1210
1433
|
return unless @auto_stop_fired
|
|
@@ -1223,9 +1446,11 @@ module Harnex
|
|
|
1223
1446
|
|
|
1224
1447
|
def classify_exit
|
|
1225
1448
|
return "timeout" if @exit_code == 124
|
|
1449
|
+
return "failure" if task_failed? && @completion_outcome_class
|
|
1226
1450
|
return "boot_failure" if boot_failure_exit?
|
|
1227
1451
|
return "failure" if task_failed?
|
|
1228
1452
|
return "success" if @exit_code == 0 && task_complete?
|
|
1453
|
+
return "success" if @exit_code == 0 && @completion_outcome_class == "completed_with_proof"
|
|
1229
1454
|
return "success" if @exit_code == 0 && session_summary_present?
|
|
1230
1455
|
return "failure" unless @exit_code == 0
|
|
1231
1456
|
|
|
@@ -1262,6 +1487,8 @@ module Harnex
|
|
|
1262
1487
|
}
|
|
1263
1488
|
queue = build_summary_queue
|
|
1264
1489
|
record[:queue] = queue if queue
|
|
1490
|
+
orchestration = build_summary_orchestration
|
|
1491
|
+
record[:orchestration] = orchestration if orchestration
|
|
1265
1492
|
record.merge!(artifact_payload.reject { |key, _value| key == "outcome" }) if artifact_payload
|
|
1266
1493
|
record
|
|
1267
1494
|
end
|
|
@@ -1306,6 +1533,14 @@ module Harnex
|
|
|
1306
1533
|
queue
|
|
1307
1534
|
end
|
|
1308
1535
|
|
|
1536
|
+
def build_summary_orchestration
|
|
1537
|
+
orchestration = Orchestration.normalize_metadata(meta_hash)
|
|
1538
|
+
return nil unless orchestration
|
|
1539
|
+
|
|
1540
|
+
orchestration["session_id"] ||= summary_agent_session_id || session_id
|
|
1541
|
+
orchestration
|
|
1542
|
+
end
|
|
1543
|
+
|
|
1309
1544
|
def build_summary_agent
|
|
1310
1545
|
{
|
|
1311
1546
|
"cli" => adapter.key,
|
|
@@ -1425,14 +1660,25 @@ module Harnex
|
|
|
1425
1660
|
|
|
1426
1661
|
def build_summary_outcome(artifact_payload)
|
|
1427
1662
|
sidecar_outcome = artifact_payload&.dig("outcome") || {}
|
|
1428
|
-
|
|
1429
|
-
|
|
1663
|
+
outcome_class, report_status = summary_proof_classification
|
|
1664
|
+
proof_failure = %w[completed_no_activity report_missing report_invalid task_failed].include?(outcome_class)
|
|
1665
|
+
status = proof_failure ? nil : sidecar_outcome["status"]
|
|
1666
|
+
status = "no_change" if status.nil? && !proof_failure && @git_end.key?(:changed_paths) && @git_end[:changed_paths].empty?
|
|
1430
1667
|
status ||= "unknown"
|
|
1431
|
-
status = "unknown" unless
|
|
1668
|
+
status = "unknown" unless Harnex::ArtifactReport::OUTCOME_STATUSES.include?(status)
|
|
1669
|
+
source = if proof_failure
|
|
1670
|
+
"harnex_completion_gate"
|
|
1671
|
+
elsif sidecar_outcome["status"]
|
|
1672
|
+
"artifact_report"
|
|
1673
|
+
else
|
|
1674
|
+
"harnex_git_observation"
|
|
1675
|
+
end
|
|
1432
1676
|
|
|
1433
1677
|
{
|
|
1434
1678
|
"status" => status,
|
|
1435
|
-
"
|
|
1679
|
+
"class" => outcome_class,
|
|
1680
|
+
"source" => source,
|
|
1681
|
+
"report_status" => report_status,
|
|
1436
1682
|
"commit_sha" => sidecar_outcome["commit_sha"] || summary_commit_sha,
|
|
1437
1683
|
"changed_paths" => @git_end.key?(:changed_paths) ? @git_end[:changed_paths] : nil,
|
|
1438
1684
|
"loc_added" => @git_end[:loc_added],
|
|
@@ -1443,6 +1689,25 @@ module Harnex
|
|
|
1443
1689
|
}
|
|
1444
1690
|
end
|
|
1445
1691
|
|
|
1692
|
+
def summary_proof_classification
|
|
1693
|
+
return [@completion_outcome_class, @completion_report_status] if @completion_outcome_class
|
|
1694
|
+
return ["task_failed", @completion_report_status] if task_failed?
|
|
1695
|
+
|
|
1696
|
+
if artifact_report_path
|
|
1697
|
+
result = Harnex::ArtifactReport.validate(artifact_report_path, final: true)
|
|
1698
|
+
report_fresh = result.status != "missing" && artifact_report_fresh?
|
|
1699
|
+
report_accepted = Harnex::ArtifactReport.accepted_final?(result)
|
|
1700
|
+
return ["completed_with_proof", "accepted"] if report_fresh && report_accepted
|
|
1701
|
+
return ["report_rejected", "rejected"] if report_fresh && Harnex::ArtifactReport.outcome_status(result) == "rejected"
|
|
1702
|
+
return ["unknown", "stale"] if report_accepted && !report_fresh
|
|
1703
|
+
return ["unknown", result.status]
|
|
1704
|
+
end
|
|
1705
|
+
|
|
1706
|
+
return ["completed_with_activity", @completion_report_status] if task_complete? && structured_activity_observed?
|
|
1707
|
+
|
|
1708
|
+
["unknown", @completion_report_status]
|
|
1709
|
+
end
|
|
1710
|
+
|
|
1446
1711
|
def build_summary_attempt
|
|
1447
1712
|
{
|
|
1448
1713
|
"run_id" => id,
|
|
@@ -48,6 +48,8 @@ module Harnex
|
|
|
48
48
|
"task_failed" => false,
|
|
49
49
|
"done" => false,
|
|
50
50
|
"work_state" => "unknown",
|
|
51
|
+
"outcome_class" => nil,
|
|
52
|
+
"artifact_report_status" => nil,
|
|
51
53
|
"exit" => nil,
|
|
52
54
|
"exit_code" => nil,
|
|
53
55
|
"summary_out" => nil,
|
|
@@ -130,6 +132,7 @@ module Harnex
|
|
|
130
132
|
def build_from_summary(record, summary_path, fallback_repo_root)
|
|
131
133
|
meta = record["meta"] || {}
|
|
132
134
|
actual = record["actual"] || {}
|
|
135
|
+
outcome = record["outcome"] || {}
|
|
133
136
|
state = classify_summary_state(actual)
|
|
134
137
|
task_complete = !!actual["task_complete"]
|
|
135
138
|
task_failed = state == "failed" && !task_complete
|
|
@@ -144,6 +147,8 @@ module Harnex
|
|
|
144
147
|
"task_failed" => task_failed,
|
|
145
148
|
"done" => Harnex.work_done_for(state, task_complete: task_complete),
|
|
146
149
|
"work_state" => Harnex.work_state_for(state, task_complete: task_complete),
|
|
150
|
+
"outcome_class" => blank_to_nil(outcome["class"]),
|
|
151
|
+
"artifact_report_status" => blank_to_nil(outcome["report_status"]),
|
|
147
152
|
"exit" => blank_to_nil(actual["exit"]),
|
|
148
153
|
"exit_code" => actual["exit_code"],
|
|
149
154
|
"summary_out" => summary_path,
|
|
@@ -188,6 +193,8 @@ module Harnex
|
|
|
188
193
|
"task_failed" => task_failed,
|
|
189
194
|
"done" => Harnex.work_done_for(state, task_complete: task_complete),
|
|
190
195
|
"work_state" => Harnex.work_state_for(state, task_complete: task_complete),
|
|
196
|
+
"outcome_class" => nil,
|
|
197
|
+
"artifact_report_status" => nil,
|
|
191
198
|
"exit" => history_exit(status),
|
|
192
199
|
"exit_code" => nil,
|
|
193
200
|
"summary_out" => blank_to_nil(record["summary_out_path"]),
|
data/lib/harnex/version.rb
CHANGED
data/lib/harnex.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "harnex/dispatch_history"
|
|
|
8
8
|
require_relative "harnex/terminal_status"
|
|
9
9
|
require_relative "harnex/artifact_report"
|
|
10
10
|
require_relative "harnex/context_telemetry"
|
|
11
|
+
require_relative "harnex/orchestration"
|
|
11
12
|
require_relative "harnex/watcher"
|
|
12
13
|
require_relative "harnex/adapters"
|
|
13
14
|
require_relative "harnex/runtime/session_state"
|
|
@@ -31,4 +32,6 @@ require_relative "harnex/commands/recipes"
|
|
|
31
32
|
require_relative "harnex/commands/guide"
|
|
32
33
|
require_relative "harnex/commands/agents_guide"
|
|
33
34
|
require_relative "harnex/commands/doctor"
|
|
35
|
+
require_relative "harnex/commands/orchestration"
|
|
36
|
+
require_relative "harnex/commands/artifact_report"
|
|
34
37
|
require_relative "harnex/cli"
|
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.14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jikku Jose
|
|
@@ -44,11 +44,13 @@ files:
|
|
|
44
44
|
- lib/harnex/cli.rb
|
|
45
45
|
- lib/harnex/codex/app_server/client.rb
|
|
46
46
|
- lib/harnex/commands/agents_guide.rb
|
|
47
|
+
- lib/harnex/commands/artifact_report.rb
|
|
47
48
|
- lib/harnex/commands/doctor.rb
|
|
48
49
|
- lib/harnex/commands/events.rb
|
|
49
50
|
- lib/harnex/commands/guide.rb
|
|
50
51
|
- lib/harnex/commands/history.rb
|
|
51
52
|
- lib/harnex/commands/logs.rb
|
|
53
|
+
- lib/harnex/commands/orchestration.rb
|
|
52
54
|
- lib/harnex/commands/pane.rb
|
|
53
55
|
- lib/harnex/commands/recipes.rb
|
|
54
56
|
- lib/harnex/commands/run.rb
|
|
@@ -61,6 +63,7 @@ files:
|
|
|
61
63
|
- lib/harnex/context_telemetry.rb
|
|
62
64
|
- lib/harnex/core.rb
|
|
63
65
|
- lib/harnex/dispatch_history.rb
|
|
66
|
+
- lib/harnex/orchestration.rb
|
|
64
67
|
- lib/harnex/runtime/api_server.rb
|
|
65
68
|
- lib/harnex/runtime/file_change_hook.rb
|
|
66
69
|
- lib/harnex/runtime/inbox.rb
|