harnex 0.6.3 → 0.6.4

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: f547957cf6589b1d153e4cf7e462482902ea71e7e1291281524e867596ea4730
4
- data.tar.gz: 4f01e84be1c31692f2b68fa09850dd781116398ab45f3e6f0b2ad76c7f52846c
3
+ metadata.gz: 354a4709831de10c8d19c40fbc4efa5b6df3d3fb1d0459fe1f4960b986eef8ad
4
+ data.tar.gz: 43e0617dac379a90281798fb7f1bd6973177111341d25cd29a1f0c6166580ada
5
5
  SHA512:
6
- metadata.gz: eb7cd143d32da68e674bbc620d6ac6c56cb4388009e5f074ab54958288e6a02a0a31df38dae9a1713881ce0ea6315f2f8c01147a1b9b071056439bc8e3734518
7
- data.tar.gz: a3c17557910b63e987cad3497c54599044417484710b8cd7cc2f152bfb9aa43b6231ba99c11efc93c0c9788bdc7f41077d22dda30e4a13b6be724f109e0eabbf
6
+ metadata.gz: 3213b7c38243f315b9cb3dfed677c63211d7148e166393f8b599088c14d7d0128ebfd0e583068182c0fff8419e483abd8f0e0f2230c37a25beb4bb6d812d0463
7
+ data.tar.gz: 1aeb182b6e355df8dce1f8445ebfcc3bedea8c7a5655590ca3eeac900b5fdcbef38786a88ef9dc8d3d0eaf2717dc330a3e34e98965ef18af6ef6f5b105d50e95
data/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## [0.6.4] — 2026-05-06
6
+
7
+ ### Fixed
8
+
9
+ - JSON-RPC adapter (`codex app-server`): harnex now mediates Codex's
10
+ server-to-client approval requests via the protocol — auto-approves
11
+ `applyPatchApproval`, `execCommandApproval`,
12
+ `item/commandExecution/requestApproval`, and
13
+ `item/fileChange/requestApproval`. Previously the adapter rejected
14
+ every server-side request with `-32601 "Unsupported server request"`,
15
+ which meant Codex's default sandbox blocked shell exec, file changes,
16
+ git commits, and package-manager invocations whenever a dispatched
17
+ worker tried to do real work. Autonomous worker dispatches now run
18
+ cleanly under the default sandbox without needing
19
+ `--dangerously-bypass-approvals-and-sandbox` or
20
+ `-c sandbox_mode=danger-full-access`.
21
+ - `CodexAppServer#build_command` now appends operator-supplied codex
22
+ flags (passed via `harnex run codex -- -c key=value`) while still
23
+ filtering out the harnex-context entry that `--context` smuggles
24
+ through `@extra_args` (codex `app-server` rejects positional input).
25
+
26
+ ### Changed
27
+
28
+ - `--legacy-pty` is now a long-term supported fallback rather than a
29
+ deprecated path. The 0.7.0-removal plan is dropped — the legacy PTY
30
+ adapter remains the right tool for interactive/TUI use cases and for
31
+ any operator who prefers terminal-native Codex chrome. JSON-RPC
32
+ remains the default for autonomous worker dispatches and structured
33
+ observability.
34
+
35
+ ### Added
36
+
37
+ - JSON-RPC adapter: classify sub-5s pre-turn exits as `boot_failure`
38
+ (vs the existing `disconnected` terminal state), tracked by latching
39
+ on `turn/started`. `build_summary_actual` counts `boot_failure` exits
40
+ in `actual.disconnections` so early-boot deaths are not lost from
41
+ dispatch telemetry. First of three planned commits for issue #32;
42
+ remaining work (ensure-block telemetry write + optional `last_error`
43
+ capture) tracked separately.
44
+
3
45
  ## [0.6.3] — 2026-05-06
4
46
 
5
47
  ### Fixed
data/TECHNICAL.md CHANGED
@@ -330,6 +330,16 @@ The adapter reads the screen and returns a state hash:
330
330
  - Notifications (`turn/started`, `turn/completed`, `item/completed`,
331
331
  `error`, `thread/compacted`, …) fan into the events log.
332
332
  `task_complete` is the harnex-side event for `turn/completed`.
333
+ - **Approval mediation**: Codex's app-server protocol delegates
334
+ sandbox/approval decisions to the JSON-RPC client via server-to-
335
+ client requests. Harnex auto-approves `applyPatchApproval`,
336
+ `execCommandApproval`, `item/commandExecution/requestApproval`, and
337
+ `item/fileChange/requestApproval`, so autonomous workers can run
338
+ shell commands, write files, commit, and invoke package managers
339
+ under Codex's default sandbox without any bypass flag. Other
340
+ server-to-client requests (permissions, user-input, dynamic-tool,
341
+ auth-refresh) currently fall through to `-32601` until use cases
342
+ appear.
333
343
  - Disconnect is detected from JSON-RPC error responses, subprocess
334
344
  EOF, parse errors, or a server `error` notification — no screen
335
345
  regex required.
@@ -339,7 +349,7 @@ The adapter reads the screen and returns a state hash:
339
349
  - See `docs/codex-appserver.md` for the full mapping table and
340
350
  troubleshooting.
341
351
 
342
- #### Codex Adapter (legacy PTY — `--legacy-pty`, removal in 0.7.0)
352
+ #### Codex Adapter (legacy PTY — `--legacy-pty`, long-term fallback)
343
353
 
344
354
  - Launches with `--no-alt-screen` for inline screen output
345
355
  - Detects prompt by looking for `›` prefix in recent lines
@@ -34,6 +34,20 @@ module Harnex
34
34
 
35
35
  EVENTS = %w[task_complete turn_started item_completed disconnected].freeze
36
36
 
37
+ # Server→client approval requests harnex auto-approves so dispatched
38
+ # codex workers can run autonomously. Codex sends these via JSON-RPC
39
+ # when its sandbox/approval policy needs a client decision; without
40
+ # a handler the client returns -32601 and codex blocks the operation.
41
+ # Permissions / user-input / dynamic-tool / auth-refresh requests
42
+ # have richer response shapes and are deliberately not auto-handled
43
+ # — they fall through to -32601 until a use case appears.
44
+ APPROVAL_RESPONSES = {
45
+ "applyPatchApproval" => { decision: "approved" },
46
+ "execCommandApproval" => { decision: "approved" },
47
+ "item/commandExecution/requestApproval" => { decision: "approved" },
48
+ "item/fileChange/requestApproval" => { decision: "accept" }
49
+ }.freeze
50
+
37
51
  attr_reader :thread_id, :current_turn_id, :last_completed_at, :initial_prompt
38
52
 
39
53
  def initialize(extra_args = [])
@@ -56,8 +70,13 @@ module Harnex
56
70
  ["codex", "app-server"]
57
71
  end
58
72
 
73
+ # The harnex-context entry (set by `--context`) is delivered via
74
+ # JSON-RPC `turn/start`, not as a CLI argument — codex app-server
75
+ # rejects positional input and would exit immediately. Operator-
76
+ # supplied codex flags (passed via `harnex run codex -- ...`) are
77
+ # appended so e.g. `-c sandbox_mode=danger-full-access` works.
59
78
  def build_command
60
- base_command
79
+ base_command + cli_extra_args
61
80
  end
62
81
 
63
82
  def describe
@@ -120,6 +139,7 @@ module Harnex
120
139
  end
121
140
 
122
141
  @client.on_notification { |msg| handle_notification(msg) }
142
+ @client.on_request { |method, params| handle_server_request(method, params) }
123
143
  @client.on_disconnect { |err| handle_disconnect(err) }
124
144
  @client.start
125
145
  perform_handshake
@@ -127,6 +147,13 @@ module Harnex
127
147
  self
128
148
  end
129
149
 
150
+ # Auto-approve known approval-style requests so dispatched workers
151
+ # can run without a human-in-the-loop. Returns the response body to
152
+ # serialize as JSON-RPC `result`, or `nil` to fall through to -32601.
153
+ def handle_server_request(method, _params)
154
+ APPROVAL_RESPONSES[method]
155
+ end
156
+
130
157
  def dispatch(prompt:, model: nil, effort: nil)
131
158
  ensure_open!
132
159
  ensure_thread!
@@ -200,6 +227,12 @@ module Harnex
200
227
  nil
201
228
  end
202
229
 
230
+ # Codex CLI flags only — strips the harnex-context entry that
231
+ # `--context` smuggles through @extra_args.
232
+ def cli_extra_args
233
+ @extra_args.reject { |a| a.is_a?(String) && a.start_with?("[harnex session id=") }
234
+ end
235
+
203
236
  def perform_handshake
204
237
  @client.request("initialize", {
205
238
  clientInfo: {
@@ -270,6 +303,7 @@ module Harnex
270
303
  @id_mutex = Mutex.new
271
304
  @write_mutex = Mutex.new
272
305
  @notification_handler = nil
306
+ @request_handler = nil
273
307
  @disconnect_handler = nil
274
308
  @disconnect_signaled = false
275
309
  @closed = false
@@ -280,6 +314,13 @@ module Harnex
280
314
  @notification_handler = block
281
315
  end
282
316
 
317
+ # Handler for server-initiated requests (id + method). The block
318
+ # receives (method, params) and returns the response body for the
319
+ # JSON-RPC `result` field, or nil to reject with -32601.
320
+ def on_request(&block)
321
+ @request_handler = block
322
+ end
323
+
283
324
  def on_disconnect(&block)
284
325
  @disconnect_handler = block
285
326
  end
@@ -381,11 +422,7 @@ module Harnex
381
422
 
382
423
  def dispatch_message(message)
383
424
  if message["id"] && message["method"]
384
- write_line({
385
- jsonrpc: "2.0",
386
- id: message["id"],
387
- error: { code: -32601, message: "Unsupported server request: #{message['method']}" }
388
- })
425
+ handle_server_request(message)
389
426
  return
390
427
  end
391
428
 
@@ -406,6 +443,29 @@ module Harnex
406
443
  @notification_handler&.call(message) if message["method"]
407
444
  end
408
445
 
446
+ def handle_server_request(message)
447
+ result =
448
+ begin
449
+ @request_handler&.call(message["method"], message["params"] || {})
450
+ rescue StandardError
451
+ nil
452
+ end
453
+
454
+ if result.nil?
455
+ write_line({
456
+ jsonrpc: "2.0",
457
+ id: message["id"],
458
+ error: { code: -32601, message: "Unsupported server request: #{message['method']}" }
459
+ })
460
+ else
461
+ write_line({
462
+ jsonrpc: "2.0",
463
+ id: message["id"],
464
+ result: result
465
+ })
466
+ end
467
+ end
468
+
409
469
  def signal_disconnect(error)
410
470
  return if @disconnect_signaled
411
471
 
@@ -18,7 +18,8 @@ module Harnex
18
18
 
19
19
  # Phase 3 flipped the default — `codex` resolves to CodexAppServer.
20
20
  # Legacy PTY adapter is reachable via `legacy_pty: true` (driven by
21
- # `harnex run codex --legacy-pty`). Will be removed in 0.7.0.
21
+ # `harnex run codex --legacy-pty`); kept as a long-term supported
22
+ # fallback for interactive/TUI use cases.
22
23
  def codex_appserver_enabled?
23
24
  true
24
25
  end
@@ -37,8 +37,9 @@ module Harnex
37
37
  --timeout SECS Max seconds to wait for detached registration (default: #{DEFAULT_TIMEOUT})
38
38
  --inbox-ttl SECS Expire queued inbox messages after SECS (default: #{Inbox::DEFAULT_TTL})
39
39
  --legacy-pty (codex only) Use the legacy PTY adapter instead of
40
- the JSON-RPC `app-server` adapter. Deprecated; will
41
- be removed in 0.7.0.
40
+ the JSON-RPC `app-server` adapter. Long-term
41
+ supported fallback for interactive/TUI use; JSON-RPC
42
+ remains the default for autonomous dispatches.
42
43
  -h, --help Show this help
43
44
 
44
45
  Notes:
@@ -74,6 +74,7 @@ module Harnex
74
74
  @usage_summary = {}
75
75
  @ended_at = nil
76
76
  @exit_reason = nil
77
+ @turn_started_seen = false
77
78
  @last_completed_at = nil
78
79
  @writer = nil
79
80
  @pid = nil
@@ -377,6 +378,7 @@ module Harnex
377
378
  when "thread/started"
378
379
  @rpc_thread_id = params["threadId"] || params["thread_id"]
379
380
  when "turn/started"
381
+ @turn_started_seen = true
380
382
  @state_machine.force_busy!
381
383
  emit_event("turn_started", turnId: params["turnId"] || params["turn_id"])
382
384
  when "turn/completed"
@@ -707,12 +709,21 @@ module Harnex
707
709
 
708
710
  def classify_exit
709
711
  return "timeout" if @exit_code == 124
712
+ return "success" if @exit_code == 0 && session_summary_present?
713
+ return "boot_failure" if boot_failure_exit?
710
714
  return "failure" unless @exit_code == 0
711
- return "success" if session_summary_present?
712
715
 
713
716
  "disconnected"
714
717
  end
715
718
 
719
+ def boot_failure_exit?
720
+ return false unless adapter.transport == :stdio_jsonrpc
721
+ return false if @turn_started_seen
722
+
723
+ lifetime = (@ended_at || Time.now) - @started_at
724
+ lifetime <= 5
725
+ end
726
+
716
727
  def session_summary_present?
717
728
  @usage_summary.values.any? { |value| !value.nil? }
718
729
  end
@@ -761,7 +772,9 @@ module Harnex
761
772
 
762
773
  def build_summary_actual
763
774
  counters = @event_counters.snapshot
764
- counters[:disconnections] = [counters[:disconnections], 1].max if @exit_reason == "disconnected"
775
+ if %w[disconnected boot_failure].include?(@exit_reason)
776
+ counters[:disconnections] = [counters[:disconnections], 1].max
777
+ end
765
778
 
766
779
  {
767
780
  model: meta_hash["model"],
@@ -1,4 +1,4 @@
1
1
  module Harnex
2
- VERSION = "0.6.3"
2
+ VERSION = "0.6.4"
3
3
  RELEASE_DATE = "2026-05-06"
4
4
  end
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.6.3
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jikku Jose