agent-harness 0.28.2 → 0.28.3

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: 1e26544498cd552f3992c81ebe33b869b51daf38e199c6f0cdc733437f39f531
4
- data.tar.gz: 55d5a1527f9ef1c208706dbfccbce73246751a329f87ff64a514e14301d81283
3
+ metadata.gz: d913e0f2d865b5da590d18bd9b1a62dfc6c2620996c374b844eaff5401b040b2
4
+ data.tar.gz: ef5f41a73da2695a54ca6a8a4c30b169c6c17ec284c5ea718b2cb0eb60edaf9d
5
5
  SHA512:
6
- metadata.gz: 9fd530fe7ab66107b79b776d170523d9095212b0988f6b2e8e5a160b792927f758b951faf1c4a5ef2fa2035d86d2ab8a1d60185c4f0631bb3c66d0d7740153bd
7
- data.tar.gz: 8b4f298b243c6185965633e41b0f629648ae308ab8d2acd62f47a73256e1cac22398cc77442b28ca69d8b77afd933415a45e678bf60a1744007d1151db0d3961
6
+ metadata.gz: '0838d91fa198bed8eac755a4e8204afbf788c583f4beb6b9e5535337f4b332fe23f9cfb6fc897464002808032c66846f775abafcd6accd967609b89117a25d01'
7
+ data.tar.gz: 8d8e3670c0ae6936ed508851de2693a13a4fb17207ec6105e6f4128857b226beb6de6a75638c177c15a933b3fb8d654988a48d3e7c61c8cffa3a765246deb5b8
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.28.2"
2
+ ".": "0.28.3"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -6,6 +6,13 @@
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.3](https://github.com/viamin/agent-harness/compare/agent-harness/v0.28.2...agent-harness/v0.28.3) (2026-07-10)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * Kilocode: default external_directory permission blocks /tmp scratch files, silently killing non-interactive runs ([#285](https://github.com/viamin/agent-harness/issues/285)) ([d703c0a](https://github.com/viamin/agent-harness/commit/d703c0abb7b91fb7a5ba8081e612486edff5e8f5))
15
+
9
16
  ## [0.28.2](https://github.com/viamin/agent-harness/compare/agent-harness/v0.28.1...agent-harness/v0.28.2) (2026-07-10)
10
17
 
11
18
 
@@ -13,6 +13,18 @@ module AgentHarness
13
13
  DEFAULT_VERSION = "7.1.3"
14
14
  SUPPORTED_VERSION_REQUIREMENT = "= #{DEFAULT_VERSION}"
15
15
  STRUCTURED_EVENT_TYPES = %w[text error step_finish result usage].freeze
16
+ # Kilo CLI (an OpenCode fork) ships the same external_directory
17
+ # permission category as OpenCode, defaulting to "ask" for anything
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/**"
25
+ DEFAULT_PERMISSION_CONFIG = {
26
+ "external_directory" => {DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERN => "allow"}
27
+ }.freeze
16
28
  USAGE_EVENT_TYPES = %w[result usage].freeze
17
29
  TOKEN_USAGE_KEYS = %w[
18
30
  input_tokens
@@ -106,21 +118,6 @@ module AgentHarness
106
118
  end
107
119
  end
108
120
 
109
- # Kilo CLI (an opencode fork) ships the same `external_directory`
110
- # permission category as OpenCode, defaulting to `ask` for anything
111
- # outside the project dir. In non-interactive execution there is no human
112
- # to answer the resulting permission prompt, so reads/writes of scratch
113
- # files under `/tmp/*` via the dedicated read/write/edit tools are
114
- # auto-rejected — even though bash I/O to the same paths succeeds (shell
115
- # redirection is not a tool call). The agent then silently loses access to
116
- # its own scratch output and never completes the task. This default rule
117
- # broadens the allowlist to all of `/tmp` so the agent can read back files
118
- # it (or its sub-agents) wrote there.
119
- DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERN = "/tmp/**"
120
- DEFAULT_PERMISSION_RULE = {
121
- "external_directory" => {DEFAULT_PERMISSION_EXTERNAL_DIRECTORY_PATTERN => "allow"}
122
- }.freeze
123
-
124
121
  def name
125
122
  "kilocode"
126
123
  end
@@ -185,10 +182,9 @@ module AgentHarness
185
182
  provider_name = options[:provider_name] || "openai"
186
183
  model_id = options[:model_id]
187
184
 
188
- config = {provider: {provider_name => {}}}
189
- config[:model] = "#{provider_name}/#{model_id}" if model_id
190
- config[:permission] = deep_dup(options[:permission]) if options[:permission]
191
- apply_default_external_directory_permission(config)
185
+ config = {"provider" => {provider_name => {}}}
186
+ config["model"] = "#{provider_name}/#{model_id}" if model_id
187
+ apply_default_external_directory_permission(config, options)
192
188
 
193
189
  config.to_json
194
190
  end
@@ -219,6 +215,31 @@ module AgentHarness
219
215
  cmd
220
216
  end
221
217
 
218
+ # Materialize the permissive external_directory permission (and any other
219
+ # runtime config extras) to +~/.config/kilocode/kilo.json+ before the CLI
220
+ # boots. This is the runtime path that actually fixes #282: without it the
221
+ # permission lives only in {config_file_content}'s return value, which no
222
+ # execution caller invokes, so Kilo would boot with stock "ask" defaults
223
+ # and auto-reject /tmp reads in non-interactive runs. Mirrors the OpenCode
224
+ # precedent in #280.
225
+ def build_execution_preparation(options)
226
+ runtime = options[:provider_runtime]
227
+ return nil unless runtime
228
+
229
+ config_payload = kilocode_config_payload(runtime)
230
+ return nil unless config_payload
231
+
232
+ ExecutionPreparation.new(
233
+ file_writes: [
234
+ {
235
+ path: kilocode_config_path,
236
+ content: serialize_kilocode_config(config_payload),
237
+ mode: 0o600
238
+ }
239
+ ]
240
+ )
241
+ end
242
+
222
243
  def parse_response(result, duration:)
223
244
  output = result.stdout
224
245
  tokens = nil
@@ -349,12 +370,49 @@ module AgentHarness
349
370
 
350
371
  private
351
372
 
352
- def apply_default_external_directory_permission(payload)
353
- # Respect any caller-supplied `permission` block verbatim: if a caller
354
- # takes responsibility for permission config, we do not override it.
355
- return if payload.key?(:permission) || payload.key?("permission")
373
+ # Default-merge a permissive external_directory permission into the
374
+ # generated Kilo config unless the caller takes responsibility for
375
+ # permission config. A caller-supplied permission (passed via the
376
+ # +:permission+ option or already present on the config under the
377
+ # +"permission"+ key, e.g. from runtime metadata config extras) is
378
+ # honored verbatim and never overridden, and an invalid or empty
379
+ # caller permission is ignored in favor of the default rule.
380
+ def apply_default_external_directory_permission(config, options = {})
381
+ caller_permission = options[:permission] || options["permission"] || config["permission"]
382
+ if caller_permission.is_a?(Hash) && !caller_permission.empty?
383
+ config["permission"] = deep_dup(caller_permission)
384
+ return
385
+ end
386
+
387
+ config["permission"] = deep_dup(DEFAULT_PERMISSION_CONFIG)
388
+ end
389
+
390
+ def kilocode_config_path
391
+ "~/.config/kilocode/kilo.json"
392
+ end
356
393
 
357
- payload[:permission] = deep_dup(DEFAULT_PERMISSION_RULE)
394
+ def kilocode_config_payload(runtime)
395
+ metadata = runtime.metadata
396
+ config_extras = metadata[:config] || metadata["config"] || {}
397
+ unless config_extras.is_a?(Hash)
398
+ raise ArgumentError,
399
+ "Kilocode runtime metadata config must be a Hash of provider-specific extras (got #{config_extras.class})"
400
+ end
401
+
402
+ payload = stringify_keys(config_extras)
403
+ payload["model"] = runtime.model if runtime.model
404
+ apply_default_external_directory_permission(payload)
405
+ payload.empty? ? nil : payload
406
+ end
407
+
408
+ def serialize_kilocode_config(payload)
409
+ JSON.pretty_generate(payload)
410
+ end
411
+
412
+ def stringify_keys(hash)
413
+ hash.each_with_object({}) do |(key, value), result|
414
+ result[key.to_s] = value
415
+ end
358
416
  end
359
417
 
360
418
  def heartbeat_hook_script(heartbeat_file_path)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AgentHarness
4
- VERSION = "0.28.2"
4
+ VERSION = "0.28.3"
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.2
4
+ version: 0.28.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan