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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d913e0f2d865b5da590d18bd9b1a62dfc6c2620996c374b844eaff5401b040b2
4
- data.tar.gz: ef5f41a73da2695a54ca6a8a4c30b169c6c17ec284c5ea718b2cb0eb60edaf9d
3
+ metadata.gz: 0e59433136b9fefab1e54397b752f0843b05db3b4f26dfd27a3607650ff3b4ad
4
+ data.tar.gz: 6043894c347081129f4486795bcb6d0d1f95750ac980dabe464e889ee0266bd2
5
5
  SHA512:
6
- metadata.gz: '0838d91fa198bed8eac755a4e8204afbf788c583f4beb6b9e5535337f4b332fe23f9cfb6fc897464002808032c66846f775abafcd6accd967609b89117a25d01'
7
- data.tar.gz: 8d8e3670c0ae6936ed508851de2693a13a4fb17207ec6105e6f4128857b226beb6de6a75638c177c15a933b3fb8d654988a48d3e7c61c8cffa3a765246deb5b8
6
+ metadata.gz: 480f739434416487cef969aa1f21bbb772d5694099418468604c41be315da9f0cbe8d2167bfb2c3e9f9620cb62d162b725ac414959b5e9cd3723c867265a6625
7
+ data.tar.gz: 9d625423fd5a2956567626074c66479d951b3d4988684fb8da12b414b3500bd4535050e6fef33e46e0c84792b6bb2599b58a2a757b07d9ef0879971d967aacb3
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.28.3"
2
+ ".": "0.28.5"
3
3
  }
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 expresses the minimum Codex
31
- # CLI version required to drive that model. Keep entries here only when
32
- # the requirement is durable runner contract knowledge — not
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 three concrete outcomes:
282
+ # +cli_version+. The contract surfaces these concrete outcomes:
280
283
  #
281
- # 1. **Supported** — the model is in {BASELINE_SUPPORTED_MODELS} (or
282
- # a known CLI-gated model whose minimum CLI version is met).
283
- # 2. **Unsupported due to CLI version** the model is known to need
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
- # 3. **Unknown / dynamic** — the model is not in this static
287
- # contract. Callers must treat this as "ask the provider" rather
288
- # than as approval.
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
- minimum_version = gated_fact[:minimum_cli_version]
316
- requirement = Gem::Requirement.new(">= #{minimum_version}")
317
- comparable_version = comparable_cli_version(normalized_cli_version)
318
-
319
- # A CLI-gated model without a comparable installed version must
320
- # stay explicit. Returning :supported here would re-introduce the
321
- # exact `gpt-5.5` failure class this contract is designed to
322
- # prevent — a caller that cannot supply a version would get
323
- # `supported? == true` and may still schedule a run onto an old
324
- # CLI (e.g. 0.115.x). Surface :unknown with the requirement
325
- # attached so callers can decide deliberately.
326
- if comparable_version.nil?
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::UNKNOWN_CLI_VERSION_REASON,
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
- unless requirement.satisfied_by?(comparable_version)
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: false,
347
- reason: AgentHarness::ModelCompatibility::UNSUPPORTED_CLI_VERSION_REASON,
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 reads of scratch files under /tmp are
20
- # auto-rejected and the agent silently loses access to its own scratch
21
- # output, killing the run without a recoverable error. This default rule
22
- # broadens the allowlist to all of /tmp so the agent can read back files
23
- # it (or its sub-agents) wrote there. See #282 (precedent: #277/#280).
24
- DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERN = "/tmp/**"
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" => {DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERN => "allow"}
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
- "/tmp/**" => "allow"
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 the agent silently
136
- # loses access to its own scratch output and never completes the task.
137
- # This default rule broadens the allowlist to all of `/tmp` so the agent
138
- # can read back files it (or its sub-agents) wrote there.
139
- DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERN = "/tmp/**"
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" => {DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERN => "allow"}
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AgentHarness
4
- VERSION = "0.28.3"
4
+ VERSION = "0.28.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agent-harness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.3
4
+ version: 0.28.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan