agent-harness 0.28.3 → 0.28.5
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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +14 -0
- data/lib/agent_harness/model_compatibility.rb +9 -0
- data/lib/agent_harness/providers/codex.rb +95 -33
- data/lib/agent_harness/providers/kilocode.rb +16 -7
- data/lib/agent_harness/providers/opencode.rb +23 -9
- data/lib/agent_harness/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: 0e59433136b9fefab1e54397b752f0843b05db3b4f26dfd27a3607650ff3b4ad
|
|
4
|
+
data.tar.gz: 6043894c347081129f4486795bcb6d0d1f95750ac980dabe464e889ee0266bd2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 480f739434416487cef969aa1f21bbb772d5694099418468604c41be315da9f0cbe8d2167bfb2c3e9f9620cb62d162b725ac414959b5e9cd3723c867265a6625
|
|
7
|
+
data.tar.gz: 9d625423fd5a2956567626074c66479d951b3d4988684fb8da12b414b3500bd4535050e6fef33e46e0c84792b6bb2599b58a2a757b07d9ef0879971d967aacb3
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@
|
|
|
6
6
|
* **auth:** add provider-owned PKCE code-exchange API for Claude OAuth (`AgentHarness::Authentication.exchange_code`). Takes an authorization code plus PKCE verifier (and `redirect_uri`/`client_id`), posts an `authorization_code` grant to the Claude token endpoint, and persists the resulting access/refresh tokens in the native `claudeAiOauth` shape. Adds `exchange_code_supported?` and a `code_exchange` key to `auth_capabilities` ([#266](https://github.com/viamin/agent-harness/issues/266)).
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [0.28.5](https://github.com/viamin/agent-harness/compare/agent-harness/v0.28.4...agent-harness/v0.28.5) (2026-07-10)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* Kilocode/OpenCode: external_directory permission also blocks ~/.config and ~/.local/share paths, not just /tmp ([#290](https://github.com/viamin/agent-harness/issues/290)) ([7042f57](https://github.com/viamin/agent-harness/commit/7042f572e5723120159c96b735906edeede6ef76))
|
|
15
|
+
|
|
16
|
+
## [0.28.4](https://github.com/viamin/agent-harness/compare/agent-harness/v0.28.3...agent-harness/v0.28.4) (2026-07-10)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* Codex model_compatibility returns 'unknown' for models definitively unsupported with subscription auth (e.g. gpt-5.5-pro) ([#287](https://github.com/viamin/agent-harness/issues/287)) ([a6f87ce](https://github.com/viamin/agent-harness/commit/a6f87ce07a2514e069559d57d8496dd323bd0891))
|
|
22
|
+
|
|
9
23
|
## [0.28.3](https://github.com/viamin/agent-harness/compare/agent-harness/v0.28.2...agent-harness/v0.28.3) (2026-07-10)
|
|
10
24
|
|
|
11
25
|
|
|
@@ -31,12 +31,21 @@ module AgentHarness
|
|
|
31
31
|
# result. Distinct from :unknown_model — the runner *does* know the
|
|
32
32
|
# model; it just cannot confirm the installed CLI is new enough.
|
|
33
33
|
UNKNOWN_CLI_VERSION_REASON = :cli_version_unknown
|
|
34
|
+
# Issued when the runner needs an auth mode to answer definitively for
|
|
35
|
+
# an auth-gated model but the caller did not supply one. Pairs with
|
|
36
|
+
# :supported_auth_modes on the result details. Distinct from
|
|
37
|
+
# :unknown_model — the runner *does* know the model; it just cannot
|
|
38
|
+
# confirm the requested auth mode is allowed for it.
|
|
39
|
+
UNKNOWN_AUTH_MODE_REASON = :auth_mode_unknown
|
|
34
40
|
# Issued when the runner supports the model but the installed CLI is too
|
|
35
41
|
# old. Pairs with :minimum_cli_version on the result.
|
|
36
42
|
UNSUPPORTED_CLI_VERSION_REASON = :cli_version_too_old
|
|
37
43
|
# Issued when the runner supports the model but the requested auth mode
|
|
38
44
|
# is not part of the runner's contract for it.
|
|
39
45
|
UNSUPPORTED_AUTH_MODE_REASON = :auth_mode_not_supported
|
|
46
|
+
# Issued when the runner accepts the requested auth mode generally, but
|
|
47
|
+
# the specific model is not available under that auth mode.
|
|
48
|
+
UNSUPPORTED_AUTH_MODE_FOR_MODEL_REASON = :unsupported_auth_mode_for_model
|
|
40
49
|
# Default supported reason.
|
|
41
50
|
SUPPORTED_REASON = :supported
|
|
42
51
|
|
|
@@ -27,9 +27,11 @@ module AgentHarness
|
|
|
27
27
|
# instead of relying on whichever model the CLI's default points at.
|
|
28
28
|
DEFAULT_COMPATIBLE_MODEL_ID = "gpt-5-codex"
|
|
29
29
|
|
|
30
|
-
# Known CLI-gated model facts. Each entry
|
|
31
|
-
# CLI version required to drive
|
|
32
|
-
#
|
|
30
|
+
# Known CLI-gated model facts. Each entry may express a minimum Codex
|
|
31
|
+
# CLI version required to drive the model, and/or auth-mode
|
|
32
|
+
# restrictions that are part of the durable runner contract. Keep
|
|
33
|
+
# entries here only when the requirement is durable runner contract
|
|
34
|
+
# knowledge — not
|
|
33
35
|
# provider-side experiments or one-off CLI defaults.
|
|
34
36
|
#
|
|
35
37
|
# The +gpt-5.5+ entry tracks the failure class observed in
|
|
@@ -37,7 +39,8 @@ module AgentHarness
|
|
|
37
39
|
# CLI builds (e.g. 0.115.x) could not drive the +gpt-5.5+ family.
|
|
38
40
|
MODEL_COMPATIBILITY_FACTS = {
|
|
39
41
|
"gpt-5.5" => {minimum_cli_version: "0.116.0"},
|
|
40
|
-
"gpt-5.5-codex" => {minimum_cli_version: "0.116.0"}
|
|
42
|
+
"gpt-5.5-codex" => {minimum_cli_version: "0.116.0"},
|
|
43
|
+
"gpt-5.5-pro" => {auth_modes: [:api_key].freeze}
|
|
41
44
|
}.each_value(&:freeze).freeze
|
|
42
45
|
|
|
43
46
|
# Models that the runner contract considers supported on every Codex
|
|
@@ -276,16 +279,25 @@ module AgentHarness
|
|
|
276
279
|
#
|
|
277
280
|
# Returns an {AgentHarness::ModelCompatibility::Result} for the
|
|
278
281
|
# combination of +model_id+, +auth_mode+, and installed
|
|
279
|
-
# +cli_version+. The contract surfaces
|
|
282
|
+
# +cli_version+. The contract surfaces these concrete outcomes:
|
|
280
283
|
#
|
|
281
|
-
# 1. **Supported** — the model is in {BASELINE_SUPPORTED_MODELS}
|
|
282
|
-
# a known CLI-gated model whose
|
|
283
|
-
#
|
|
284
|
+
# 1. **Supported** — the model is in {BASELINE_SUPPORTED_MODELS}, or
|
|
285
|
+
# a known CLI-gated model whose stated restrictions are met (a
|
|
286
|
+
# minimum Codex CLI version and/or an auth-mode restriction; some
|
|
287
|
+
# gated models express only one of these).
|
|
288
|
+
# 2. **Unsupported due to auth mode** — the requested auth mode is
|
|
289
|
+
# not accepted by the runner at all, or is not available for the
|
|
290
|
+
# specific model (e.g. an api-key-only model requested with
|
|
291
|
+
# subscription auth).
|
|
292
|
+
# 3. **Unsupported due to CLI version** — the model is known to need
|
|
284
293
|
# a newer Codex CLI than was supplied; the result carries
|
|
285
294
|
# +:minimum_cli_version+ so callers can act on it.
|
|
286
|
-
#
|
|
287
|
-
# contract
|
|
288
|
-
#
|
|
295
|
+
# 4. **Unknown / dynamic** — the model is not in this static
|
|
296
|
+
# contract, or a gated model was queried without the gating input
|
|
297
|
+
# needed to answer definitively (an auth mode for an
|
|
298
|
+
# auth-restricted model, or an installed CLI version for a
|
|
299
|
+
# version-gated model). Callers must treat this as "ask the
|
|
300
|
+
# provider" rather than as approval.
|
|
289
301
|
#
|
|
290
302
|
# @param model_id [String, Symbol]
|
|
291
303
|
# @param auth_mode [Symbol, nil] :api_key or :subscription
|
|
@@ -312,42 +324,94 @@ module AgentHarness
|
|
|
312
324
|
|
|
313
325
|
gated_fact = MODEL_COMPATIBILITY_FACTS[normalized_model_id]
|
|
314
326
|
if gated_fact
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
+
supported_auth_modes = gated_fact[:auth_modes]
|
|
328
|
+
if normalized_auth_mode && supported_auth_modes && !supported_auth_modes.include?(normalized_auth_mode)
|
|
329
|
+
return AgentHarness::ModelCompatibility.build_result(
|
|
330
|
+
runner: provider_name,
|
|
331
|
+
model_id: normalized_model_id,
|
|
332
|
+
auth_mode: normalized_auth_mode,
|
|
333
|
+
cli_version: normalized_cli_version,
|
|
334
|
+
supported: false,
|
|
335
|
+
reason: AgentHarness::ModelCompatibility::UNSUPPORTED_AUTH_MODE_FOR_MODEL_REASON,
|
|
336
|
+
fallback_model_id: DEFAULT_COMPATIBLE_MODEL_ID,
|
|
337
|
+
source: :static_contract,
|
|
338
|
+
details: {supported_auth_modes: supported_auth_modes}
|
|
339
|
+
)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# An auth-gated model queried without an auth mode must stay
|
|
343
|
+
# explicit. Returning :supported here would re-introduce the
|
|
344
|
+
# exact permissive false-positive this contract is designed to
|
|
345
|
+
# prevent — `auth_mode` defaults to nil on the public API, so a
|
|
346
|
+
# caller that queries availability without an auth mode and
|
|
347
|
+
# treats `supported? == true` as approval could then schedule an
|
|
348
|
+
# api-key-only model (e.g. gpt-5.5-pro) under subscription. This
|
|
349
|
+
# mirrors the sibling CLI-version dimension below: when the
|
|
350
|
+
# gating input is missing, surface :unknown with the allowed
|
|
351
|
+
# auth modes attached so callers can decide deliberately.
|
|
352
|
+
if supported_auth_modes && normalized_auth_mode.nil?
|
|
327
353
|
return AgentHarness::ModelCompatibility.unknown_result(
|
|
328
354
|
runner: provider_name,
|
|
329
355
|
model_id: normalized_model_id,
|
|
330
356
|
auth_mode: normalized_auth_mode,
|
|
331
357
|
cli_version: normalized_cli_version,
|
|
332
|
-
reason: AgentHarness::ModelCompatibility::
|
|
333
|
-
minimum_cli_version: minimum_version,
|
|
334
|
-
cli_version_requirement: requirement.to_s,
|
|
358
|
+
reason: AgentHarness::ModelCompatibility::UNKNOWN_AUTH_MODE_REASON,
|
|
335
359
|
fallback_model_id: DEFAULT_COMPATIBLE_MODEL_ID,
|
|
336
|
-
source: :static_contract
|
|
360
|
+
source: :static_contract,
|
|
361
|
+
details: {supported_auth_modes: supported_auth_modes}
|
|
337
362
|
)
|
|
338
363
|
end
|
|
339
364
|
|
|
340
|
-
|
|
365
|
+
minimum_version = gated_fact[:minimum_cli_version]
|
|
366
|
+
if minimum_version
|
|
367
|
+
requirement = Gem::Requirement.new(">= #{minimum_version}")
|
|
368
|
+
comparable_version = comparable_cli_version(normalized_cli_version)
|
|
369
|
+
|
|
370
|
+
# A CLI-gated model without a comparable installed version must
|
|
371
|
+
# stay explicit. Returning :supported here would re-introduce the
|
|
372
|
+
# exact `gpt-5.5` failure class this contract is designed to
|
|
373
|
+
# prevent — a caller that cannot supply a version would get
|
|
374
|
+
# `supported? == true` and may still schedule a run onto an old
|
|
375
|
+
# CLI (e.g. 0.115.x). Surface :unknown with the requirement
|
|
376
|
+
# attached so callers can decide deliberately.
|
|
377
|
+
if comparable_version.nil?
|
|
378
|
+
return AgentHarness::ModelCompatibility.unknown_result(
|
|
379
|
+
runner: provider_name,
|
|
380
|
+
model_id: normalized_model_id,
|
|
381
|
+
auth_mode: normalized_auth_mode,
|
|
382
|
+
cli_version: normalized_cli_version,
|
|
383
|
+
reason: AgentHarness::ModelCompatibility::UNKNOWN_CLI_VERSION_REASON,
|
|
384
|
+
minimum_cli_version: minimum_version,
|
|
385
|
+
cli_version_requirement: requirement.to_s,
|
|
386
|
+
fallback_model_id: DEFAULT_COMPATIBLE_MODEL_ID,
|
|
387
|
+
source: :static_contract
|
|
388
|
+
)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
unless requirement.satisfied_by?(comparable_version)
|
|
392
|
+
return AgentHarness::ModelCompatibility.build_result(
|
|
393
|
+
runner: provider_name,
|
|
394
|
+
model_id: normalized_model_id,
|
|
395
|
+
auth_mode: normalized_auth_mode,
|
|
396
|
+
cli_version: normalized_cli_version,
|
|
397
|
+
supported: false,
|
|
398
|
+
reason: AgentHarness::ModelCompatibility::UNSUPPORTED_CLI_VERSION_REASON,
|
|
399
|
+
minimum_cli_version: minimum_version,
|
|
400
|
+
cli_version_requirement: requirement.to_s,
|
|
401
|
+
fallback_model_id: DEFAULT_COMPATIBLE_MODEL_ID,
|
|
402
|
+
source: :static_contract
|
|
403
|
+
)
|
|
404
|
+
end
|
|
405
|
+
|
|
341
406
|
return AgentHarness::ModelCompatibility.build_result(
|
|
342
407
|
runner: provider_name,
|
|
343
408
|
model_id: normalized_model_id,
|
|
344
409
|
auth_mode: normalized_auth_mode,
|
|
345
410
|
cli_version: normalized_cli_version,
|
|
346
|
-
supported:
|
|
347
|
-
reason: AgentHarness::ModelCompatibility::
|
|
411
|
+
supported: true,
|
|
412
|
+
reason: AgentHarness::ModelCompatibility::SUPPORTED_REASON,
|
|
348
413
|
minimum_cli_version: minimum_version,
|
|
349
414
|
cli_version_requirement: requirement.to_s,
|
|
350
|
-
fallback_model_id: DEFAULT_COMPATIBLE_MODEL_ID,
|
|
351
415
|
source: :static_contract
|
|
352
416
|
)
|
|
353
417
|
end
|
|
@@ -359,8 +423,6 @@ module AgentHarness
|
|
|
359
423
|
cli_version: normalized_cli_version,
|
|
360
424
|
supported: true,
|
|
361
425
|
reason: AgentHarness::ModelCompatibility::SUPPORTED_REASON,
|
|
362
|
-
minimum_cli_version: minimum_version,
|
|
363
|
-
cli_version_requirement: requirement.to_s,
|
|
364
426
|
source: :static_contract
|
|
365
427
|
)
|
|
366
428
|
end
|
|
@@ -16,14 +16,23 @@ module AgentHarness
|
|
|
16
16
|
# Kilo CLI (an OpenCode fork) ships the same external_directory
|
|
17
17
|
# permission category as OpenCode, defaulting to "ask" for anything
|
|
18
18
|
# outside the project dir. In non-interactive execution there is no
|
|
19
|
-
# human to answer the prompt, so
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
19
|
+
# human to answer the prompt, so dedicated read/write/edit tool calls
|
|
20
|
+
# targeting paths outside the project dir are auto-rejected and the
|
|
21
|
+
# agent silently loses access to its own scratch output or its own
|
|
22
|
+
# config/data files, killing the run without a recoverable error.
|
|
23
|
+
#
|
|
24
|
+
# This default rule broadens the allowlist to all of /tmp (so the agent
|
|
25
|
+
# can read back files it or its sub-agents wrote there) and to the full
|
|
26
|
+
# agent home directory (so the agent can inspect/maintain its own
|
|
27
|
+
# config, cache, and data files such as ~/.config/kilocode,
|
|
28
|
+
# ~/.local/share/kilo, ~/.cache). The container is already isolated
|
|
29
|
+
# (Docker, non-root user) and the agent runs with --auto which approves
|
|
30
|
+
# everything inside the project dir. See #289 (precedent: #282/#277).
|
|
31
|
+
DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERNS = ["/tmp/**", "/home/agent/**"].freeze
|
|
25
32
|
DEFAULT_PERMISSION_CONFIG = {
|
|
26
|
-
"external_directory" =>
|
|
33
|
+
"external_directory" => DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERNS
|
|
34
|
+
.to_h { |pattern| [pattern, "allow"] }
|
|
35
|
+
.freeze
|
|
27
36
|
}.freeze
|
|
28
37
|
USAGE_EVENT_TYPES = %w[result usage].freeze
|
|
29
38
|
TOKEN_USAGE_KEYS = %w[
|
|
@@ -13,10 +13,14 @@ module AgentHarness
|
|
|
13
13
|
SUPPORTED_CLI_VERSION = "1.3.2"
|
|
14
14
|
SUPPORTED_CLI_REQUIREMENT = Gem::Requirement.new(">= #{SUPPORTED_CLI_VERSION}", "< 1.4.0").freeze
|
|
15
15
|
INSTALL_COMMAND_PREFIX = ["npm", "install", "-g", "--ignore-scripts"].freeze
|
|
16
|
+
# Allowlist of external_directory patterns auto-approved in
|
|
17
|
+
# non-interactive execution. See the DEFAULT_PERMISSION_RULE comment
|
|
18
|
+
# below for the rationale (precedent: #289/#282/#277/#280).
|
|
19
|
+
DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERNS = ["/tmp/**", "/home/agent/**"].freeze
|
|
16
20
|
DEFAULT_PERMISSION_CONFIG = {
|
|
17
|
-
"external_directory" =>
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
"external_directory" => DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERNS
|
|
22
|
+
.to_h { |pattern| [pattern, "allow"] }
|
|
23
|
+
.freeze
|
|
20
24
|
}.freeze
|
|
21
25
|
SUPPORTED_CLI_VERSIONS = [SUPPORTED_CLI_VERSION].freeze
|
|
22
26
|
POSTINSTALL_COMMAND = "node $(npm root -g)/opencode-ai/postinstall.mjs"
|
|
@@ -132,13 +136,23 @@ module AgentHarness
|
|
|
132
136
|
# `/tmp/opencode/*`, but its own sub-agent delegation pattern (e.g. the
|
|
133
137
|
# Explore-Agent used to summarize large diffs/outputs) writes scratch
|
|
134
138
|
# files to other `/tmp/*` paths. In non-interactive execution there is no
|
|
135
|
-
# human to approve the resulting permission prompt, so
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
|
|
139
|
+
# human to approve the resulting permission prompt, so dedicated
|
|
140
|
+
# read/write/edit tool calls targeting paths outside the project dir are
|
|
141
|
+
# auto-rejected and the agent silently loses access to its own scratch
|
|
142
|
+
# output (or its own config/data files) and never completes the task.
|
|
143
|
+
#
|
|
144
|
+
# This default rule broadens the allowlist (see
|
|
145
|
+
# DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERNS) to all of `/tmp` (so
|
|
146
|
+
# the agent can read back files it or its sub-agents wrote there) and to
|
|
147
|
+
# the full agent home directory (so the agent can inspect/maintain its
|
|
148
|
+
# own config, cache, and data files such as ~/.config/opencode,
|
|
149
|
+
# ~/.local/share/opencode, ~/.cache). The container is already isolated
|
|
150
|
+
# (Docker, non-root user) and the agent runs with --auto which approves
|
|
151
|
+
# everything inside the project dir. See #289 (precedent: #282/#277/#280).
|
|
140
152
|
DEFAULT_PERMISSION_RULE = {
|
|
141
|
-
"external_directory" =>
|
|
153
|
+
"external_directory" => DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERNS
|
|
154
|
+
.to_h { |pattern| [pattern, "allow"] }
|
|
155
|
+
.freeze
|
|
142
156
|
}.freeze
|
|
143
157
|
|
|
144
158
|
def name
|