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 +4 -4
- data/CHANGELOG.md +42 -0
- data/TECHNICAL.md +11 -1
- data/lib/harnex/adapters/codex_appserver.rb +66 -6
- data/lib/harnex/adapters.rb +2 -1
- data/lib/harnex/commands/run.rb +3 -2
- data/lib/harnex/runtime/session.rb +15 -2
- data/lib/harnex/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 354a4709831de10c8d19c40fbc4efa5b6df3d3fb1d0459fe1f4960b986eef8ad
|
|
4
|
+
data.tar.gz: 43e0617dac379a90281798fb7f1bd6973177111341d25cd29a1f0c6166580ada
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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`,
|
|
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
|
-
|
|
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
|
|
data/lib/harnex/adapters.rb
CHANGED
|
@@ -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`)
|
|
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
|
data/lib/harnex/commands/run.rb
CHANGED
|
@@ -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.
|
|
41
|
-
|
|
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
|
-
|
|
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"],
|
data/lib/harnex/version.rb
CHANGED