harnex 0.7.12 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da71f86b7e940d085a54ae59ce7f70a9579ca23204213cbc41a017fd5d11ed9f
4
- data.tar.gz: 194f3ac0c6fabf118e74542dd4836c5014721cdf0000973b03be07dceceeee3e
3
+ metadata.gz: e50f27f3d17436e058126d656fdaeb3dfd58cbfdb0e565b8f221ae6b579f3507
4
+ data.tar.gz: 01f08ae4414ed2e6a5d4d4503888349c7e27b7aee348b805a435f393ba0f95ad
5
5
  SHA512:
6
- metadata.gz: 7975b8610b1d21c230fb0ccf7b55c16bdd9e9ff71c770bdb42f831e687313e47c9e01b20157fc8f95c767bdf716e3501fcb56a490d37dfb277f7ac1bd03fda74
7
- data.tar.gz: 54e0d45f362d94efa2d0d698911c2d2b1daaf534c97be95bef99512d3c6ab8ad44bdb6d099eaff6d5bb38e08acee563901d94db528ab0b03df8a856648c88dd5
6
+ metadata.gz: f62483a74ed5651eecc43c2e30f5020ddd077b0038a17522309af411335c86b94a04d45eec39a2d33defcbb0d781b7ac1c3085c34bd24d0ab1f9baaeb5ebbc32
7
+ data.tar.gz: 896793c0dc92d82f62989105714be633fc3b4966752fa3049d7258529cc31fbad2ac0ef1ff36bec2c5b9ac1b2baf42ac325d96f3fe9228f8efbfd0b61bc0e906
data/CHANGELOG.md CHANGED
@@ -2,6 +2,77 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.7.14] - 2026-07-15 | 10:06 PM | IST
6
+
7
+ ### Added
8
+
9
+ - `harnex run` now accepts opt-in orchestration metadata flags
10
+ (`--orchestration-run-id`, `--orchestration-generation-id`,
11
+ `--orchestration-role`, `--orchestration-session-id`, and
12
+ `--orchestration-rotation-reason`) and emits a top-level `orchestration`
13
+ block when present.
14
+ - New `harnex orchestration sample` / `harnex orchestration report` commands
15
+ ingest bounded external-primary samples and compute primary-versus-worker
16
+ queue rollups without storing prompts, transcripts, tool payloads, or secrets.
17
+ - New `harnex artifact-report init PATH` and `harnex artifact-report validate
18
+ PATH [--final]` commands provide a bounded v1 report skeleton and
19
+ machine-readable field diagnostics without echoing report payloads.
20
+ - `harnex run --require-artifact-report` makes fresh accepted/no-change report
21
+ proof part of the work verdict and exposes strict mode to workers through
22
+ `HARNEX_ARTIFACT_REPORT_REQUIRED=1`.
23
+
24
+ ### Changed
25
+
26
+ - Autonomous Codex app-server turns launched with `--context` now distinguish
27
+ provider turn completion from accepted work completion. Harnex requires
28
+ structured command/tool activity, a Git delta, or a fresh accepted/no-change
29
+ report before emitting `task_complete`; the classifier never parses final
30
+ prose and applies equally to flex and fast service tiers.
31
+ - Dispatch outcomes now include additive `class` and `report_status` fields,
32
+ and watch/wait/status/marker payloads preserve typed proof failures.
33
+
34
+ ### Fixed
35
+
36
+ - Acknowledgment-only Codex turns now emit `completed_no_activity` and return a
37
+ non-zero work/auto-stop verdict instead of being reported as successful work.
38
+ - Strict report mode fails closed for missing, malformed, unsupported,
39
+ oversized, schema-incomplete, rejected, and unchanged stale sidecars. A valid
40
+ explicit `no_change` report remains an accepted proof path for intentional
41
+ no-delta work, while report-shaped JSON printed in final prose is ignored.
42
+
43
+ ## [0.7.13] - 2026-07-15 | 12:24 PM | IST
44
+
45
+ ### Added
46
+
47
+ - Dispatch summaries now include a stable additive `context` block with
48
+ terminal and peak active-context pressure, source/status provenance, bounded
49
+ sample counts, and explicit missing-sample handling. Pi RPC reports observed
50
+ `get_session_stats.contextUsage`; Codex app-server conservatively estimates
51
+ pressure from `tokenUsage.last` plus `modelContextWindow`; unsupported
52
+ adapters do not fabricate values.
53
+ - Dispatch summaries now include additive `usage`, `attribution`, `outcome`, and
54
+ per-session `attempt` blocks. `usage.status` distinguishes provider-observed,
55
+ explicit-zero, caller-estimated, unsupported, and missing values so nullable
56
+ cost/token fields are never mistaken for zero.
57
+ - `harnex run` accepts `--parent-dispatch-id`, `--parent-attempt-id`, and
58
+ `--attempt-kind` (`initial`, `retry`, `fix`, `review`, or `superseding`) to
59
+ preserve parent/child relationships while retaining separate raw effort rows.
60
+ - Terminal event streams now emit `attempt_started` / `attempt_finished` and
61
+ translate Pi internal retry notices into `attempt_retry_scheduled`; the
62
+ Session exposes an `attempt_fallback_switched` telemetry seam for recovery
63
+ and fallback owners.
64
+ - Bounded artifact reports can optionally record a semantic outcome
65
+ (`accepted`, `rejected`, `no_change`, or `unknown`). Final outcome blocks
66
+ retain git commit/path/LOC observations without claiming they prove authorship.
67
+
68
+ ### Changed
69
+
70
+ - Dispatch telemetry documentation now distinguishes cumulative usage from
71
+ active context-window pressure and defines context sample/high-water/null
72
+ semantics alongside usage-cost provenance, strict attribution quality,
73
+ sidecar-backed outcomes, attempt linkage, and safe throughput/reliability
74
+ query examples.
75
+
5
76
  ## [0.7.12] - 2026-07-10 | 11:19 PM | IST
6
77
 
7
78
  ### Fixed
data/README.md CHANGED
@@ -203,8 +203,14 @@ For file-change hooks, use `--watch-file PATH`. The older
203
203
  `--watch PATH`/`--watch=PATH` form is still accepted for compatibility, while
204
204
  bare `--watch` means babysitter mode.
205
205
 
206
- For one-shot startup prompts, add `--auto-stop`. It requires `--context`
207
- and stops the session after the first task completion or PTY prompt return.
206
+ For one-shot startup prompts, add `--auto-stop`. It requires `--context`.
207
+ A Codex app-server turn launched from `--context` only counts as accepted
208
+ autonomous work when structured command/tool activity, a Git delta, or a fresh
209
+ accepted/no-change artifact report proves activity. An acknowledgment-only turn emits
210
+ `outcome.class=completed_no_activity`, makes `watch --until done` return
211
+ non-zero, and auto-stops with a non-zero verdict instead of reporting success.
212
+ PTY adapters still stop on prompt return because they do not expose equivalent
213
+ structured activity signals.
208
214
 
209
215
  ## Completion and waiting
210
216
 
@@ -269,20 +275,35 @@ harnex run pi --meta '{"read_budget_lines":2000,"output_ceiling_lines":800}' ...
269
275
  ```
270
276
 
271
277
  Workers can also write a small machine-readable proof sidecar while keeping the
272
- canonical explanation in plain-text `koder/` files:
278
+ canonical explanation in plain-text `koder/` files. Initialize the real schema
279
+ instead of asking a model to reproduce it from prose:
273
280
 
274
281
  ```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' \
282
+ harnex artifact-report init .harnex/reports/pi-i-61.json
283
+ harnex run pi --id pi-i-61 \
284
+ --artifact-report .harnex/reports/pi-i-61.json \
285
+ --require-artifact-report \
286
+ --context 'Run validation, update the canonical koder artifact, finalize $HARNEX_ARTIFACT_REPORT_PATH, and validate it with harnex artifact-report validate "$HARNEX_ARTIFACT_REPORT_PATH" --final' \
278
287
  --auto-stop
279
288
  ```
280
289
 
281
290
  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.
291
+ `HARNEX_ARTIFACT_REPORT_PATH` / `HARNEX_VALIDATION_REPORT_PATH`, the schema as
292
+ `HARNEX_ARTIFACT_REPORT_SCHEMA`, and strict mode as
293
+ `HARNEX_ARTIFACT_REPORT_REQUIRED=1`. `harnex artifact-report validate PATH`
294
+ checks field shapes; add `--final` to require `status=pass`, an
295
+ `accepted`/`no_change` outcome with a summary, valid command exit codes, and
296
+ `validation.final_reported=true`. Diagnostics contain bounded field paths and
297
+ shape errors, not report payloads or transcripts.
298
+
299
+ Without `--require-artifact-report`, report defects remain fail-soft warning
300
+ telemetry. With it, a missing, malformed, unsupported, oversized,
301
+ contract-incomplete, rejected, or unchanged stale report makes the work verdict
302
+ non-zero. A fresh explicit `no_change` report can prove an intentional no-delta
303
+ task without fake edits. Harnex records compact `artifact_report`, `validation`,
304
+ and `artifacts` blocks plus `outcome.class` / `outcome.report_status`; Git
305
+ changes alone never imply semantic acceptance, and JSON printed in final prose
306
+ is never scraped as a sidecar.
286
307
 
287
308
  Queue runners can pass first-class attribution without hiding it in prose:
288
309
 
@@ -293,13 +314,36 @@ harnex run pi --project-id harnex --queue-id queue-005 --entry-id SP-4 \
293
314
 
294
315
  Soft budget metadata is copied into summary `meta`; queue/agent/reliability
295
316
  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,
299
- task-completion state, operational counters (`stalls`, `force_resumes`,
300
- `disconnections`, `tool_calls`, `commands_executed`), output/event log paths,
301
- and rough volume measurements such as `lines_changed`, `output_lines`,
302
- `output_bytes`, and `event_records`.
317
+ blocks. Every terminal row also has `usage` (so null is distinguishable from
318
+ explicit zero or an estimate), `attribution`, `outcome`, and a joinable
319
+ per-session `attempt` block. Terminal summary `actual` records timing, exit
320
+ classification, token usage when the adapter can capture it, adapter-reported
321
+ `cost_usd` when reliably available, git deltas, task-completion state,
322
+ operational counters (`stalls`, `force_resumes`, `disconnections`,
323
+ `tool_calls`, `commands_executed`), output/event log paths, and rough volume
324
+ measurements such as `lines_changed`, `output_lines`, `output_bytes`, and
325
+ `event_records`.
326
+
327
+ Long queue runners can also opt into logical primary-orchestrator rollups:
328
+
329
+ ```bash
330
+ harnex run pi --orchestration-run-id queue-005 --orchestration-generation-id gen-1 \
331
+ --orchestration-role worker --project-id harnex --queue-id queue-005 \
332
+ --entry-id SP-4 --phase implement --intent queue-work ...
333
+
334
+ harnex orchestration sample --out .harnex/orchestrator.jsonl \
335
+ --run-id queue-005 --generation-id primary-1 --session-id pi-primary-1 \
336
+ --context-tokens 64000 --context-window-tokens 200000 \
337
+ --usage-status observed --usage-total-tokens 129000 --tool-calls 31
338
+
339
+ harnex orchestration report --dispatch .harnex/dispatch.jsonl \
340
+ --samples .harnex/orchestrator.jsonl --run-id queue-005 --json
341
+ ```
342
+
343
+ External samples use the bounded `harnex.orchestrator_sample.v1` schema. They
344
+ record ids, lifecycle/sample event names, aggregate usage/context counters,
345
+ tool-call counts, compactions, and rotation reasons only; prompts, transcripts,
346
+ tool arguments/results, secrets, and private payloads are out of scope.
303
347
  Harnex records the data only; consumers decide whether to fail closed. See
304
348
  [docs/dispatch-telemetry.md](docs/dispatch-telemetry.md) for the field
305
349
  contract.
@@ -378,6 +422,7 @@ See [recipes/03_buddy.md](recipes/03_buddy.md) for the full pattern.
378
422
  | `harnex events --id <id>` | Stream structured session events (`--snapshot` for non-blocking dump) |
379
423
  | `harnex history` | List completed dispatches from `.harnex/dispatch.jsonl` |
380
424
  | `harnex wait --id <id>` | Block until process exit by default; use `--until done` for unattended work completion or `--until task_complete` for exact structured turn completion |
425
+ | `harnex artifact-report init\|validate PATH` | Create or validate bounded `harnex.artifact_report.v1` proof; use `validate --final` before strict completion |
381
426
  | `harnex doctor` | Run adapter dependency preflight checks; add `--sweep` for read-only session drift diagnostics |
382
427
  | `harnex guide` | Getting started walkthrough |
383
428
  | `harnex agents-guide` | Agent-facing dispatch, chain, buddy, monitoring, and naming guides |
data/TECHNICAL.md CHANGED
@@ -28,10 +28,29 @@ harnex run codex -- --cd ~/other/repo
28
28
  | `--preset NAME` | Watch preset (`impl`, `plan`, `gate`), requires `--watch` |
29
29
  | `--watch-file PATH` | Auto-send a file-change hook (`--watch PATH`/`--watch=PATH` legacy) |
30
30
  | `--context TXT` | Give the agent a task on startup |
31
- | `--auto-stop` | With `--context`, stop after the first task completion |
31
+ | `--auto-stop` | With `--context`, stop after accepted task completion |
32
+ | `--artifact-report PATH` | Expose and ingest a bounded v1 proof sidecar |
33
+ | `--require-artifact-report` | Fail closed unless the sidecar is accepted final proof |
32
34
  | `--fast` | For Codex, use `service_tier="fast"` instead of default `flex` |
33
35
  | `--timeout SEC` | Wait budget for detached registration |
34
36
 
37
+ Codex app-server auto-stop rejects completion with no structured command/tool
38
+ activity, Git delta, or fresh accepted/no-change report as
39
+ `completed_no_activity`. Strict artifact-report mode works across transports
40
+ and returns non-zero for missing, invalid, rejected, or stale proof.
41
+
42
+ ### `harnex artifact-report` — Initialize and validate proof
43
+
44
+ ```bash
45
+ harnex artifact-report init .harnex/reports/worker.json
46
+ harnex artifact-report validate .harnex/reports/worker.json --final
47
+ ```
48
+
49
+ `init` writes a schema-valid in-progress skeleton (`--force` replaces an
50
+ existing file). `validate` emits bounded machine-readable field diagnostics;
51
+ `--final` additionally requires accepted/no-change final proof and successful
52
+ validation records.
53
+
35
54
  ### `harnex send` — Talk to a running agent
36
55
 
37
56
  ```bash
@@ -22,6 +22,9 @@ Inside a harnex-managed session, these environment variables are available:
22
22
  | `HARNEX_SESSION_REPO_ROOT` | Repo root for the session |
23
23
  | `HARNEX_SESSION_ID` | Internal harnex instance ID |
24
24
  | `HARNEX_SPAWNER_PANE` | Tmux pane ID of the invoker |
25
+ | `HARNEX_ARTIFACT_REPORT_PATH` | Absolute configured proof-sidecar path |
26
+ | `HARNEX_ARTIFACT_REPORT_SCHEMA` | Required sidecar schema identifier |
27
+ | `HARNEX_ARTIFACT_REPORT_REQUIRED` | `1` when report proof is fail-closed |
25
28
 
26
29
  Use `harnex send`, `harnex status`, `harnex wait`, `harnex pane`, and
27
30
  `harnex logs` to coordinate with peers. If you are not inside harnex,
@@ -64,9 +67,14 @@ harnex run pi --id pi-i-NN --tmux pi-i-NN \
64
67
  ```
65
68
 
66
69
  For one-shot context dispatches that should clean themselves up, add
67
- `--auto-stop`. It requires `--context`, fires once on the first task
68
- completion, and does not keep the session alive for later reuse. This keeps
69
- parallel orchestration compact:
70
+ `--auto-stop`. It requires `--context` and does not keep the session alive for
71
+ later reuse. On Codex app-server, a turn launched from `--context` is accepted
72
+ only after structured command/tool activity, Git delta, or fresh
73
+ accepted/no-change sidecar proof. A prose-only acknowledgment emits
74
+ `completed_no_activity`, is
75
+ visible as `task_failed` before teardown, and exits non-zero. This keeps
76
+ parallel orchestration compact without converting agent turn completion into
77
+ accepted work completion:
70
78
 
71
79
  ```bash
72
80
  for i in 1 2 3; do
@@ -96,20 +104,32 @@ harnex run codex --cwd /tmp/leximaze_eval_run_001 \
96
104
  child process cwd. Neither flag is a sandbox.
97
105
 
98
106
  For queue closeout, ask workers to write a compact sidecar in addition to their
99
- plain-text `koder/` artifact:
107
+ plain-text `koder/` artifact. For blind/unattended work, initialize and require
108
+ the report so a normal final answer cannot bypass proof acceptance:
100
109
 
101
110
  ```bash
111
+ harnex artifact-report init .harnex/reports/pi-i-NN.json
102
112
  harnex run pi --id pi-i-NN --tmux pi-i-NN \
103
113
  --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' \
114
+ --require-artifact-report \
115
+ --context 'Update the canonical koder file, finalize $HARNEX_ARTIFACT_REPORT_PATH, then run harnex artifact-report validate "$HARNEX_ARTIFACT_REPORT_PATH" --final' \
105
116
  --auto-stop
106
117
  ```
107
118
 
108
119
  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.
120
+ machine-readable proof in the sidecar: an `accepted` or `no_change` outcome,
121
+ validation command/status/exit codes, typed artifact summaries (`finding`,
122
+ `review`, `gate`, `blocker`, etc.), evidence, confidence, and canonical refs.
123
+ `harnex artifact-report validate PATH` checks the schema without echoing report
124
+ payloads; `--final` additionally requires accepted final proof and
125
+ `validation.final_reported=true`.
126
+
127
+ Without `--require-artifact-report`, missing/malformed reports remain warning
128
+ telemetry. Strict mode fails closed for missing, malformed, unsupported,
129
+ oversized, schema-incomplete, rejected, or unchanged stale reports. JSON in the
130
+ agent's final prose does not count: only the configured sidecar path is read.
131
+ A fresh valid `no_change` report is the explicit proof path for intentional
132
+ no-delta work.
113
133
 
114
134
  Queue runners should pass first-class attribution so dispatch rows can be grouped
115
135
  without path/id heuristics:
@@ -23,9 +23,12 @@ For unattended monitors on existing visible/detached sessions, prefer
23
23
  exits `0`, failed work exits non-zero, and wall-clock caps exit `124`. For
24
24
  callers that need the lower-level primitive, `harnex wait --until done` exposes
25
25
  the same work fence. For structured sessions (Pi RPC and Codex app-server),
26
- `harnex wait --until task_complete` remains the exact successful-turn fence.
27
- None of these know your acceptance criteria; verify the expected artifact or
28
- tests afterward.
26
+ `harnex wait --until task_complete` remains the exact accepted-turn fence.
27
+ Codex acknowledgment-only auto-stop turns are typed
28
+ `completed_no_activity` and fail this fence without transcript parsing.
29
+ `--require-artifact-report` can additionally make sidecar shape/final-proof
30
+ acceptance part of the verdict. Harnex still does not judge semantic quality;
31
+ verify the expected artifact or tests afterward.
29
32
 
30
33
  ## Completion Test
31
34
 
@@ -103,8 +106,11 @@ harnex watch --id pi-i-NN --until done --max-wait 90m \
103
106
  ```
104
107
 
105
108
  If that exits `124`, inspect the pane/logs/events and decide whether to nudge,
106
- stop, or continue. If it exits any other non-zero code, treat the work as
107
- failed; do not continue polling the same task as though it were still running.
109
+ stop, or continue. If it exits any other non-zero code, inspect
110
+ `outcome_class` / `artifact_report_status` in the JSON or fail marker, treat the
111
+ work as failed, and do not continue polling the same task as though it were
112
+ still running. `completed_no_activity`, `report_missing`, `report_invalid`, and
113
+ `report_rejected` are work-acceptance failures rather than successful turns.
108
114
 
109
115
  Recommended caps:
110
116
 
@@ -73,6 +73,22 @@ module Harnex
73
73
  {}
74
74
  end
75
75
 
76
+ # Whether this adapter has a supported source for token/cost telemetry.
77
+ # A supported source can still be missing for a particular run.
78
+ def usage_telemetry_supported?
79
+ false
80
+ end
81
+
82
+ # Whether this adapter exposes active context-window occupancy separately
83
+ # from cumulative usage. Supported sources can still yield no valid sample.
84
+ def context_telemetry_supported?
85
+ false
86
+ end
87
+
88
+ def context_telemetry_source
89
+ nil
90
+ end
91
+
76
92
  def send_wait_seconds(submit:, enter_only:)
77
93
  0.0
78
94
  end
@@ -60,6 +60,10 @@ module Harnex
60
60
  end
61
61
  end
62
62
 
63
+ def usage_telemetry_supported?
64
+ true
65
+ end
66
+
63
67
  def parse_session_summary(transcript_tail)
64
68
  summary = empty_session_summary
65
69
  text = transcript_tail.to_s
@@ -74,6 +74,18 @@ module Harnex
74
74
  "openai"
75
75
  end
76
76
 
77
+ def usage_telemetry_supported?
78
+ true
79
+ end
80
+
81
+ def context_telemetry_supported?
82
+ true
83
+ end
84
+
85
+ def context_telemetry_source
86
+ "codex_thread_token_usage_last"
87
+ end
88
+
77
89
  def base_command
78
90
  ["codex", "app-server"]
79
91
  end
@@ -31,6 +31,10 @@ module Harnex
31
31
  @write_mutex = Mutex.new
32
32
  @summary_mutex = Mutex.new
33
33
  @session_summary = {}
34
+ @context_telemetry = ContextTelemetry.new(
35
+ status: "observed",
36
+ source: context_telemetry_source
37
+ )
34
38
  @model = nil
35
39
  @provider = nil
36
40
  @session_stats_requested = false
@@ -49,6 +53,18 @@ module Harnex
49
53
  @model
50
54
  end
51
55
 
56
+ def usage_telemetry_supported?
57
+ true
58
+ end
59
+
60
+ def context_telemetry_supported?
61
+ true
62
+ end
63
+
64
+ def context_telemetry_source
65
+ "pi_get_session_stats"
66
+ end
67
+
52
68
  def base_command
53
69
  ["pi", "--mode", "rpc"]
54
70
  end
@@ -147,11 +163,17 @@ module Harnex
147
163
 
148
164
  def request_session_stats_async
149
165
  return if @closed
150
- return if @session_stats_requested
151
166
 
152
- @session_stats_requested = true
167
+ should_request = @summary_mutex.synchronize do
168
+ next false if @session_stats_requested
169
+
170
+ @session_stats_requested = true
171
+ end
172
+ return unless should_request
173
+
153
174
  write_line("type" => "get_session_stats")
154
175
  rescue StandardError
176
+ clear_session_stats_request
155
177
  nil
156
178
  end
157
179
 
@@ -181,8 +203,10 @@ module Harnex
181
203
  agent_session_id: @session_summary[:agent_session_id],
182
204
  tool_calls: @session_summary[:tool_calls],
183
205
  cost_usd: @session_summary[:cost_usd],
206
+ cost_source: @session_summary[:cost_source],
184
207
  model: @session_summary[:model],
185
- agent_provider: @session_summary[:agent_provider]
208
+ agent_provider: @session_summary[:agent_provider],
209
+ context: @context_telemetry.snapshot
186
210
  }
187
211
  end
188
212
  end
@@ -263,8 +287,7 @@ module Harnex
263
287
  end
264
288
 
265
289
  def attempt_live_summary_refresh
266
- response = request("type" => "get_session_stats")
267
- absorb_session_stats(response)
290
+ request("type" => "get_session_stats")
268
291
  rescue StandardError
269
292
  nil
270
293
  end
@@ -338,6 +361,8 @@ module Harnex
338
361
  @state = :prompt
339
362
  @last_completed_at = Time.now
340
363
  request_session_stats_async
364
+ when "compaction_end"
365
+ request_session_stats_async
341
366
  when "message_end"
342
367
  absorb_model_from_message(message["message"])
343
368
  end
@@ -362,6 +387,8 @@ module Harnex
362
387
  return unless data.is_a?(Hash)
363
388
 
364
389
  tokens = data["tokens"] || {}
390
+ context_usage = data["contextUsage"]
391
+ context_usage = {} unless context_usage.is_a?(Hash)
365
392
  @summary_mutex.synchronize do
366
393
  @session_summary[:input_tokens] = numeric_or_nil(tokens["input"])
367
394
  @session_summary[:output_tokens] = numeric_or_nil(tokens["output"])
@@ -369,10 +396,24 @@ module Harnex
369
396
  @session_summary[:total_tokens] = numeric_or_nil(tokens["total"])
370
397
  @session_summary[:tool_calls] = numeric_or_nil(data["toolCalls"])
371
398
  @session_summary[:cost_usd] = float_or_nil(data["cost"])
399
+ @session_summary[:cost_source] = "provider_reported" unless @session_summary[:cost_usd].nil?
372
400
  @session_summary[:agent_session_id] = data["sessionId"] if data["sessionId"]
373
401
  @session_summary[:model] = @model if @model
374
402
  @session_summary[:agent_provider] = @provider if @provider
403
+ @context_telemetry.record(
404
+ tokens: context_usage["tokens"],
405
+ window_tokens: context_usage["contextWindow"],
406
+ percent: context_usage["percent"]
407
+ )
375
408
  end
409
+ ensure
410
+ clear_session_stats_request
411
+ end
412
+
413
+ def clear_session_stats_request
414
+ @summary_mutex.synchronize { @session_stats_requested = false }
415
+ rescue StandardError
416
+ @session_stats_requested = false
376
417
  end
377
418
 
378
419
  def absorb_model_from_message(message)