@nbardy/oompa 0.4.4 → 0.4.6

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.
@@ -141,8 +141,8 @@
141
141
  [harness model]
142
142
  (try
143
143
  (let [cmd (case harness
144
- :claude ["claude" "--model" model "-p" "say ok" "--max-turns" "1"]
145
- :codex ["codex" "exec" "--dangerously-bypass-approvals-and-sandbox" "--skip-git-repo-check" "--model" model "--" "say ok"])
144
+ :claude ["claude" "--model" model "-p" "[oompa:probe] say ok" "--max-turns" "1"]
145
+ :codex ["codex" "exec" "--dangerously-bypass-approvals-and-sandbox" "--skip-git-repo-check" "--model" model "--" "[oompa:probe] say ok"])
146
146
  null-in (io/input-stream (io/file "/dev/null"))
147
147
  proc (process/process cmd {:out :string :err :string :in null-in})
148
148
  result (deref proc 30000 :timeout)]
@@ -97,6 +97,26 @@
97
97
  "Root of the oompa package — set by bin/oompa.js, falls back to cwd."
98
98
  (or (System/getenv "OOMPA_PACKAGE_ROOT") "."))
99
99
 
100
+ ;; Resolve absolute paths for CLI binaries at first use.
101
+ ;; ProcessBuilder with :dir set can fail to find bare command names on some
102
+ ;; platforms (macOS + babashka), so we resolve once via `which` and cache.
103
+ (def ^:private binary-paths* (atom {}))
104
+
105
+ (defn- resolve-binary!
106
+ "Resolve the absolute path of a CLI binary. Caches result.
107
+ Throws if binary not found on PATH."
108
+ [name]
109
+ (or (get @binary-paths* name)
110
+ (let [result (try
111
+ (process/sh ["which" name] {:out :string :err :string})
112
+ (catch Exception _ {:exit -1 :out "" :err ""}))
113
+ path (when (zero? (:exit result))
114
+ (str/trim (:out result)))]
115
+ (if path
116
+ (do (swap! binary-paths* assoc name path)
117
+ path)
118
+ (throw (ex-info (str "Binary not found on PATH: " name) {:binary name}))))))
119
+
100
120
  (defn- load-prompt
101
121
  "Load a prompt file. Tries path as-is first, then from package root."
102
122
  [path]
@@ -172,14 +192,14 @@
172
192
  ;; Build command — both harnesses run with cwd=worktree, no sandbox
173
193
  ;; so agents can `..` to reach project root for task management
174
194
  cmd (case harness
175
- :codex (cond-> ["codex" "exec"
195
+ :codex (cond-> [(resolve-binary! "codex") "exec"
176
196
  "--dangerously-bypass-approvals-and-sandbox"
177
197
  "--skip-git-repo-check"
178
198
  "-C" abs-worktree]
179
199
  model (into ["--model" model])
180
200
  reasoning (into ["-c" (str "model_reasoning_effort=\"" reasoning "\"")])
181
201
  true (conj "--" full-prompt))
182
- :claude (cond-> ["claude" "-p" "--dangerously-skip-permissions"
202
+ :claude (cond-> [(resolve-binary! "claude") "-p" "--dangerously-skip-permissions"
183
203
  "--session-id" session-id]
184
204
  model (into ["--model" model])))
185
205
 
@@ -227,13 +247,13 @@
227
247
 
228
248
  ;; Build command — cwd=worktree, no sandbox
229
249
  cmd (case review-harness
230
- :codex (cond-> ["codex" "exec"
250
+ :codex (cond-> [(resolve-binary! "codex") "exec"
231
251
  "--dangerously-bypass-approvals-and-sandbox"
232
252
  "--skip-git-repo-check"
233
253
  "-C" abs-wt]
234
254
  review-model (into ["--model" review-model])
235
255
  true (conj "--" review-prompt))
236
- :claude (cond-> ["claude" "-p" "--dangerously-skip-permissions"]
256
+ :claude (cond-> [(resolve-binary! "claude") "-p" "--dangerously-skip-permissions"]
237
257
  review-model (into ["--model" review-model])))
238
258
 
239
259
  ;; Run reviewer — cwd=worktree
@@ -265,13 +285,13 @@
265
285
  abs-wt (.getAbsolutePath (io/file worktree-path))
266
286
 
267
287
  cmd (case harness
268
- :codex (cond-> ["codex" "exec"
288
+ :codex (cond-> [(resolve-binary! "codex") "exec"
269
289
  "--dangerously-bypass-approvals-and-sandbox"
270
290
  "--skip-git-repo-check"
271
291
  "-C" abs-wt]
272
292
  model (into ["--model" model])
273
293
  true (conj "--" fix-prompt))
274
- :claude (cond-> ["claude" "-p" "--dangerously-skip-permissions"]
294
+ :claude (cond-> [(resolve-binary! "claude") "-p" "--dangerously-skip-permissions"]
275
295
  model (into ["--model" model])))
276
296
 
277
297
  result (try
@@ -356,8 +376,16 @@
356
376
  wt-path (str project-root "/" wt-dir)]
357
377
 
358
378
  (try
359
- ;; Setup worktree (in project root) dir starts with . but branch name must be valid
360
- (process/sh ["git" "worktree" "add" wt-dir "-b" wt-branch] {:dir project-root})
379
+ ;; Clean stale worktree/branch from previous failed runs before creating
380
+ (process/sh ["git" "worktree" "remove" wt-dir "--force"] {:dir project-root})
381
+ (process/sh ["git" "branch" "-D" wt-branch] {:dir project-root})
382
+
383
+ ;; Setup worktree (in project root)
384
+ (let [wt-result (process/sh ["git" "worktree" "add" wt-dir "-b" wt-branch]
385
+ {:dir project-root :out :string :err :string})]
386
+ (when-not (zero? (:exit wt-result))
387
+ (throw (ex-info (str "Failed to create worktree: " (:err wt-result))
388
+ {:dir wt-dir :branch wt-branch}))))
361
389
 
362
390
  ;; Build context
363
391
  (let [context (build-context)
@@ -391,8 +419,9 @@
391
419
  {:status :continue})))))
392
420
 
393
421
  (finally
394
- ;; Cleanup worktree (in project root)
395
- (process/sh ["git" "worktree" "remove" wt-dir "--force"] {:dir project-root})))))
422
+ ;; Cleanup worktree and branch (in project root)
423
+ (process/sh ["git" "worktree" "remove" wt-dir "--force"] {:dir project-root})
424
+ (process/sh ["git" "branch" "-D" wt-branch] {:dir project-root})))))
396
425
 
397
426
  ;; =============================================================================
398
427
  ;; Worker Loop
@@ -400,6 +429,7 @@
400
429
 
401
430
  (def ^:private max-wait-for-tasks 60)
402
431
  (def ^:private wait-poll-interval 5)
432
+ (def ^:private max-consecutive-errors 3)
403
433
 
404
434
  (defn- wait-for-tasks!
405
435
  "Wait up to 60s for pending/current tasks to appear. Used for backpressure
@@ -436,7 +466,8 @@
436
466
  (wait-for-tasks! id))
437
467
 
438
468
  (loop [iter 1
439
- completed 0]
469
+ completed 0
470
+ consec-errors 0]
440
471
  (if (> iter iterations)
441
472
  (do
442
473
  (println (format "[%s] Completed %d iterations" id completed))
@@ -450,12 +481,18 @@
450
481
  (assoc worker :completed iter :status :done))
451
482
 
452
483
  :error
453
- (do
454
- (println (format "[%s] Worker error at iteration %d/%d, continuing..." id iter iterations))
455
- (recur (inc iter) completed))
484
+ (let [errors (inc consec-errors)]
485
+ (if (>= errors max-consecutive-errors)
486
+ (do
487
+ (println (format "[%s] %d consecutive errors, stopping worker" id errors))
488
+ (assoc worker :completed completed :status :error))
489
+ (do
490
+ (println (format "[%s] Error at iteration %d/%d (%d/%d), continuing..."
491
+ id iter iterations errors max-consecutive-errors))
492
+ (recur (inc iter) completed errors))))
456
493
 
457
494
  :continue
458
- (recur (inc iter) (inc completed))))))))
495
+ (recur (inc iter) (inc completed) 0))))))))
459
496
 
460
497
  ;; =============================================================================
461
498
  ;; Multi-Worker Execution
package/bin/test-models CHANGED
@@ -37,9 +37,9 @@ if [ -z "$MODELS" ]; then
37
37
  exit 1
38
38
  fi
39
39
 
40
- # Create results directory
40
+ # Create results directory in /tmp so it doesn't litter the project
41
41
  RUN_ID=$(python3 -c "import uuid; print(str(uuid.uuid4())[:8])")
42
- RESULTS_DIR="tst_results_${RUN_ID}"
42
+ RESULTS_DIR="/tmp/oompa_tst_${RUN_ID}"
43
43
  mkdir -p "$RESULTS_DIR"
44
44
 
45
45
  MODEL_COUNT=$(echo "$MODELS" | wc -l | tr -d ' ')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbardy/oompa",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "Git-worktree multi-agent swarm orchestrator for Codex and Claude",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",