harnex 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,175 @@
1
+ # Codex `app-server` adapter
2
+
3
+ harnex 0.6.0 talks to Codex over JSON-RPC 2.0 instead of scraping a
4
+ PTY pane. The adapter spawns `codex app-server` as a subprocess,
5
+ exchanges newline-delimited JSON-RPC messages on stdin/stdout, and
6
+ fans server notifications into the harnex events log.
7
+
8
+ ## Transport
9
+
10
+ - Subprocess: `codex app-server` (CLI ≥ 0.128.0 — verify with
11
+ `harnex doctor`).
12
+ - Wire format: one JSON object per line.
13
+ - Encoding: UTF-8.
14
+ - One `Adapter#transport` value: `:stdio_jsonrpc`.
15
+
16
+ ## Handshake
17
+
18
+ Mirrors `codex-plugin-cc/plugins/codex/scripts/lib/app-server.mjs`.
19
+
20
+ ```ruby
21
+ client.request("initialize", {
22
+ clientInfo: { title: "harnex", name: "harnex", version: Harnex::VERSION },
23
+ capabilities: {
24
+ experimentalApi: false,
25
+ optOutNotificationMethods: %w[
26
+ item/agentMessage/delta
27
+ item/reasoning/summaryTextDelta
28
+ item/reasoning/summaryPartAdded
29
+ item/reasoning/textDelta
30
+ ]
31
+ }
32
+ })
33
+ client.notify("initialized", {})
34
+ ```
35
+
36
+ After the handshake the client is ready to issue `thread/start` and
37
+ `turn/start` requests.
38
+
39
+ ## Notification → event mapping
40
+
41
+ | Server notification | harnex event | Notes |
42
+ |-----------------------------|--------------------|-------|
43
+ | `thread/started` | (metadata) | Stashes `threadId` |
44
+ | `turn/started` | `turn_started` | Carries `turnId` |
45
+ | `turn/completed` | `task_complete` or `task_failed` | Failed/interrupted statuses emit `task_failed` with the Codex error. A completed `--context` turn emits `task_complete` only with structured command/tool activity or a Git delta; otherwise it emits typed `completed_no_activity`. Harnex writes the observed-state receipt before the successful event. |
46
+ | `item/started` | (silent) | Streaming deltas opted out |
47
+ | `item/completed` | `item_completed` + synthesized transcript | See "tmux/STDOUT" below |
48
+ | `error` | `error` | Turn-level Codex error notification; preserves nested `error.message` and does not count as a transport disconnect. |
49
+ | `thread/status/changed` | (state only) | Drives state machine |
50
+ | `thread/tokenUsage/updated` | (status field) | Surfaced via `harnex status --json` |
51
+ | `thread/compacted` | `compaction` | Increments `compactions` counter |
52
+ | `account/rateLimits/updated`| (silent, status) | Visible in `status --json` |
53
+
54
+ ### How disconnects are detected
55
+
56
+ Disconnects are detected from subprocess exit / EOF on stdout and parse errors
57
+ when the server emits a malformed line. Request-level JSON-RPC error responses
58
+ reject only the in-flight request; they do not by themselves imply that the
59
+ transport disconnected. Schema-defined `error` notifications are turn-level
60
+ Codex errors and feed `last_error` / `task_failed` rather than the disconnect
61
+ counter.
62
+
63
+ Unexpected transport loss emits `disconnected` and increments
64
+ `auto_disconnects`. Normal auto-stop teardown after `task_complete` /
65
+ `task_failed` is treated as a clean structured close. There is no need for the
66
+ screen-text regex that the legacy adapter relied on.
67
+
68
+ ## tmux / STDOUT — synthesized transcript
69
+
70
+ Without a PTY, `harnex run codex --tmux` and `harnex pane --id …`
71
+ would otherwise see an empty pane. The JSON-RPC path renders a
72
+ synthesized transcript built from `item/completed` notifications:
73
+
74
+ - `agent_message` items render their text payload
75
+ - `tool_call` items render as `tool: <name> <one-line summary>`
76
+
77
+ The synthesized transcript is written to BOTH the output log AND
78
+ STDOUT, so:
79
+
80
+ - `harnex run codex` (foreground) — user sees the transcript live
81
+ - `harnex run codex --tmux` — the tmux window shows the transcript
82
+ - `harnex pane --id <session>` — captures the synthesized text
83
+ - `harnex logs --id <session>` — replays the same transcript
84
+
85
+ For interactive debugging where the original Codex TUI is wanted,
86
+ `codex resume <thread-id>` opens the same thread in a real Codex
87
+ CLI.
88
+
89
+ ## `harnex wait --until done` / `task_complete` / `task_failed`
90
+
91
+ For unattended monitors, block until Codex work completes, fails, or the session exits:
92
+
93
+ ```
94
+ harnex wait --id cx-i-242 --until done --timeout 300
95
+ ```
96
+
97
+ When you need the exact successful structured turn event, wait for `task_complete`:
98
+
99
+ ```
100
+ harnex wait --id cx-i-242 --until task_complete --timeout 300
101
+ ```
102
+
103
+ `--until done` returns non-zero when it sees `task_failed` or failed terminal
104
+ telemetry. This includes acknowledgment-only autonomous `--context` turns
105
+ (`outcome_class=completed_no_activity`) and the rare receipt-write failure
106
+ (`report_invalid`). Harnex classifies completion from app-server item counters
107
+ and Git state; it does not inspect final-answer prose or trust worker claims.
108
+ The task-complete/task-failed waiters
109
+ tail the events JSONL — not the API socket — so they keep working across
110
+ restarts and are adapter-agnostic.
111
+
112
+ Every blind dispatch receives a harness-authored receipt. No worker report is
113
+ required: Harnex captures command exits, Git state, completion, and usage, then
114
+ writes `HARNEX_ARTIFACT_REPORT_PATH` before emitting `task_complete`. Use
115
+ `--artifact-report PATH` only to choose a fixed destination; otherwise the
116
+ status/end row points to the default state-directory path. Review workers may
117
+ write advisory summary/verdict/P1-P3 counts to `HARNEX_ARTIFACT_CLAIMS_PATH`.
118
+ Claims and report-shaped `agentMessage` text cannot satisfy the activity gate.
119
+ Consumers can run `harnex artifact-report validate PATH --final` afterward.
120
+
121
+ ## `harnex doctor`
122
+
123
+ Verifies the Codex CLI is installed and at version ≥ 0.128.0. JSON
124
+ output, exit 0 if healthy.
125
+
126
+ ```
127
+ $ harnex doctor
128
+ {"ok":true,"checks":[{"name":"codex","required":">= 0.128.0","ok":true,"found":"0.128.0"}]}
129
+ ```
130
+
131
+ ## Long-term fallback: `--legacy-pty`
132
+
133
+ The pre-0.6.0 PTY adapter remains available as a long-term supported
134
+ fallback:
135
+
136
+ ```
137
+ harnex run codex --legacy-pty
138
+ ```
139
+
140
+ It's the right tool when you want the full Codex TUI live in tmux —
141
+ status bars, tool diffs, ANSI panels — that the headless `app-server`
142
+ backend doesn't render. JSON-RPC remains the default and is recommended
143
+ for autonomous worker dispatches; legacy-pty is for interactive/TUI use.
144
+
145
+ ## Troubleshooting
146
+
147
+ - **`task_complete` never fires.** Check `harnex events --id <session>` first:
148
+ failed Codex turns emit `task_failed` with the provider/model error. If there
149
+ is neither `task_complete` nor `task_failed`, run `harnex doctor`; Codex <
150
+ 0.128.0 is unsupported.
151
+ - **Empty tmux pane.** Codex hasn't emitted any `item/completed`
152
+ yet — the agent is reasoning. The pane fills as soon as the
153
+ first item completes.
154
+ - **`task_failed` immediately after dispatch.** Check
155
+ `harnex events --id <session>`. Provider/model failures retain their Codex
156
+ error message. `completed_no_activity` means the turn ended with no
157
+ command/tool or Git activity. `report_invalid` now primarily identifies a
158
+ harness receipt-write failure; old rows may still contain the legacy
159
+ `report_missing` / `report_rejected` classes. Common provider failures
160
+ include auth environment variables (for example `OPENAI_API_KEY` /
161
+ `AZURE_OPENAI_API_KEY`) and model unavailability.
162
+
163
+ ## Schema fixtures
164
+
165
+ `test/fixtures/codex_appserver/schema/` holds hand-pruned subsets
166
+ of `ServerNotification` and `ClientRequest` for the methods harnex
167
+ issues / consumes. Regenerate via:
168
+
169
+ ```
170
+ codex app-server generate-json-schema --out /tmp/codex-schema-X
171
+ ```
172
+
173
+ then re-prune. The full bundle is ~3 MB; the pruned subsets are
174
+ < 50 KB and serve as a compact reference for what's actually wired
175
+ through the adapter.
@@ -0,0 +1,118 @@
1
+ # Repository Configuration
2
+
3
+ Harnex has one optional repository configuration file:
4
+
5
+ ```text
6
+ <git-root>/.harnex/config.json
7
+ ```
8
+
9
+ It is resolved with the same git-root rule as the canonical dispatch stream.
10
+ Non-git launches use the global dispatch stream and do not invent a global
11
+ configuration file. An absent file is valid. An explicitly present malformed
12
+ file fails before agent spawn so policy is never silently ignored.
13
+
14
+ ## Phase allowlist
15
+
16
+ Repositories can normalize queue/work phase names:
17
+
18
+ ```json
19
+ {
20
+ "phase": {
21
+ "allowlist": [
22
+ "plan-write",
23
+ "plan-review",
24
+ "plan-fix",
25
+ "test-suite",
26
+ "implement",
27
+ "code-review",
28
+ "code-fix",
29
+ "mapping",
30
+ "triage",
31
+ "docs"
32
+ ],
33
+ "policy": "reject"
34
+ }
35
+ }
36
+ ```
37
+
38
+ The effective value is the first-class `harnex run --phase TEXT` value, or
39
+ `--meta.phase` when no first-class override is supplied.
40
+
41
+ - No `phase` configuration: any phase passes.
42
+ - Allowlisted phase: dispatches silently.
43
+ - `policy: "warn"`: prints a warning and dispatches.
44
+ - `policy: "reject"`: exits non-zero before spawn and writes no dispatch row.
45
+
46
+ The allowlist must be an array of non-empty strings. Policy must be `warn` or
47
+ `reject`.
48
+
49
+ ## Events, output, and receipt retention
50
+
51
+ Per-session event JSONL, output transcripts, and generated proof receipts live
52
+ under Harnex's local state directory and are not the durable dispatch stream:
53
+
54
+ ```text
55
+ ~/.local/state/harnex/events/
56
+ ~/.local/state/harnex/output/
57
+ ~/.local/state/harnex/receipts/
58
+ ```
59
+
60
+ Defaults apply independently to each directory:
61
+
62
+ - maximum age: 45 days;
63
+ - maximum total size: 1 GiB.
64
+
65
+ Override them in repo configuration:
66
+
67
+ ```json
68
+ {
69
+ "retention": {
70
+ "events": {
71
+ "max_age_days": 45,
72
+ "max_bytes": 1073741824
73
+ },
74
+ "output": {
75
+ "max_age_days": 45,
76
+ "max_bytes": 1073741824
77
+ },
78
+ "receipts": {
79
+ "max_age_days": 45,
80
+ "max_bytes": 1073741824
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ Environment variables take precedence:
87
+
88
+ ```text
89
+ HARNEX_EVENTS_MAX_AGE_DAYS
90
+ HARNEX_EVENTS_MAX_BYTES
91
+ HARNEX_OUTPUT_MAX_AGE_DAYS
92
+ HARNEX_OUTPUT_MAX_BYTES
93
+ HARNEX_RECEIPTS_MAX_AGE_DAYS
94
+ HARNEX_RECEIPTS_MAX_BYTES
95
+ ```
96
+
97
+ Limits must be positive integers. Harnex deletes only regular files directly
98
+ owned by the `events`, `output`, and `receipts` directories: expired files first, then the
99
+ oldest unprotected files until the size cap is met. It never follows paths
100
+ outside those directories. Files for the current session, live registry PIDs,
101
+ and alive uncompleted dispatch-start rows are protected. If protected files
102
+ alone exceed a cap, Harnex reports the directory over cap rather than deleting
103
+ them.
104
+
105
+ A bounded prune runs opportunistically when a dispatch starts. Inspect or force
106
+ the same policy with `doctor`:
107
+
108
+ ```bash
109
+ harnex doctor # sizes, limits, and last-prune status
110
+ harnex doctor --prune --dry-run # preview bounded candidate paths, do not delete
111
+ harnex doctor --prune # apply now
112
+ ```
113
+
114
+ `--dry-run` is valid only with `--prune`. Manual prune bypasses the automatic
115
+ cadence but preserves the same live/current safety rules.
116
+
117
+ Set `HARNEX_STATE_DIR` before launching Harnex when tests or isolated automation
118
+ need a disposable local-state root.