@nbardy/oompa 0.7.2 → 0.7.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.
@@ -8,6 +8,21 @@
8
8
  (defn now-ms []
9
9
  (System/currentTimeMillis))
10
10
 
11
+ (defn- normalize-priority
12
+ "Coerce :priority to a sortable number.
13
+ Accepts: integer, \"P1\"/\"P2\" strings, numeric strings, nil.
14
+ Unknown/nil → 1000 (low priority)."
15
+ [p]
16
+ (cond
17
+ (number? p) p
18
+ (nil? p) 1000
19
+ (string? p)
20
+ (let [s (str/upper-case (str/trim p))]
21
+ (cond
22
+ (str/starts-with? s "P") (try (Integer/parseInt (subs s 1)) (catch Exception _ 1000))
23
+ :else (try (Integer/parseInt s) (catch Exception _ 1000))))
24
+ :else 1000))
25
+
11
26
  (defn format-ago
12
27
  "Return human-readable relative time string for epoch milliseconds."
13
28
  [^long ts-ms]
@@ -50,7 +65,7 @@
50
65
 
51
66
  (defn- queue-lines [tasks]
52
67
  (->> tasks
53
- (sort-by (juxt (comp (fnil identity 1000) :priority) :id))
68
+ (sort-by (juxt (comp normalize-priority :priority) :id))
54
69
  (map (fn [{:keys [id summary]}]
55
70
  (format "`%s` • %s" id summary)))))
56
71
 
@@ -82,7 +97,7 @@
82
97
 
83
98
  (defn- backlog-entries [tasks]
84
99
  (->> tasks
85
- (sort-by (juxt (comp (fnil identity 1000) :priority) :id))
100
+ (sort-by (juxt (comp normalize-priority :priority) :id))
86
101
  (map #(select-keys % [:id :summary]))
87
102
  (remove #(nil? (:id %)))
88
103
  (take 7)
@@ -37,9 +37,9 @@
37
37
  ;; NDJSON Output Parsing (harness-specific, lives here not in agent-cli)
38
38
  ;; =============================================================================
39
39
 
40
- (defn- parse-ndjson-output
41
- "Parse NDJSON output from CLIs (opencode, gemini).
42
- Returns {:session-id string|nil, :text string|nil}."
40
+ (defn- parse-unified-jsonl-output
41
+ "Parse unified JSONL emitted by `agent-cli run`.
42
+ Returns {:session-id string|nil, :text string|nil, :warning string|nil}."
43
43
  [s]
44
44
  (let [raw (or s "")
45
45
  events (->> (str/split-lines raw)
@@ -49,27 +49,47 @@
49
49
  (catch Exception _
50
50
  nil))))
51
51
  doall)
52
- session-id (or (some #(or (:sessionID %)
53
- (:sessionId %)
54
- (:session_id %)
55
- (get-in % [:part :sessionID])
56
- (get-in % [:part :sessionId]))
57
- events)
58
- (some-> (re-find #"(ses_[A-Za-z0-9]+)" raw) second))
52
+ event-types (->> events (keep :type) distinct (take 8) vec)
53
+ session-id (some->> events
54
+ (keep #(when (= "session.started" (:type %))
55
+ (:sessionId %)))
56
+ last)
59
57
  text (->> events
60
- (keep (fn [event]
61
- (let [event-type (or (:type event) (get-in event [:part :type]))
62
- role (or (:role event) (get-in event [:part :role]))
63
- chunk (or (:text event) (get-in event [:part :text]) (:content event))]
64
- (when (and (or (= event-type "text")
65
- (and (= event-type "message")
66
- (= role "assistant")))
67
- (string? chunk)
68
- (not (str/blank? chunk)))
69
- chunk))))
70
- (str/join ""))]
58
+ (keep #(when (= "text.delta" (:type %)) (:text %)))
59
+ (remove str/blank?)
60
+ (str/join ""))
61
+ stderr-text (->> events
62
+ (keep #(when (= "stderr" (:type %)) (:text %)))
63
+ (remove str/blank?)
64
+ (str/join ""))
65
+ errors (->> events
66
+ (keep #(when (= "error" (:type %)) (:message %)))
67
+ (remove str/blank?)
68
+ vec)
69
+ warning (cond
70
+ (and (not (str/blank? raw)) (empty? events))
71
+ "agent-cli returned non-empty output, but no unified JSONL events were parsed."
72
+
73
+ (seq errors)
74
+ (str "agent-cli reported error events: " (str/join " | " (take 3 errors)))
75
+
76
+ (and (seq events) (str/blank? text))
77
+ (str "agent-cli returned unified events, but no text deltas were extracted"
78
+ " (types=" (if (seq event-types)
79
+ (str/join "," event-types)
80
+ "unknown")
81
+ ").")
82
+
83
+ :else nil)]
71
84
  {:session-id session-id
72
- :text (when-not (str/blank? text) text)}))
85
+ :text (cond
86
+ (not (str/blank? text)) text
87
+ (seq errors) (str/join "\n" errors)
88
+ (not (str/blank? stderr-text)) stderr-text
89
+ :else nil)
90
+ :warning warning
91
+ :raw-snippet (when-not (str/blank? raw)
92
+ (subs raw 0 (min 400 (count raw))))}))
73
93
 
74
94
  ;; =============================================================================
75
95
  ;; Env Helpers
@@ -154,11 +174,7 @@
154
174
 
155
175
  (defn build-cmd
156
176
  "Build CLI command vector via agent-cli JSON input.
157
- Sends a JSON dict to `agent-cli build --input -`, parses the CommandSpec,
158
- and resolves the binary to an absolute path.
159
-
160
- agent-cli owns all CLI flag syntax (session create/resume, model decomposition,
161
- bypass flags, prompt delivery). This function just maps oompa opts to JSON."
177
+ Used for probe/debug flows. Execution should prefer `run-command!`."
162
178
  [harness-kw opts]
163
179
  (let [input (-> {:harness (name harness-kw)
164
180
  :bypassPermissions true}
@@ -188,6 +204,26 @@
188
204
  :prompt prompt
189
205
  :close "")))
190
206
 
207
+ (defn run-command!
208
+ "Execute a harness through `agent-cli run`, which emits unified JSONL events."
209
+ [harness-kw opts]
210
+ (let [input (-> {:harness (name harness-kw)
211
+ :mode "conversation"
212
+ :prompt (:prompt opts)
213
+ :cwd (:cwd opts)
214
+ :yolo true}
215
+ (cond->
216
+ (:model opts) (assoc :model (:model opts))
217
+ (and (:session-id opts) (not (:resume? opts))) (assoc :sessionId (:session-id opts))
218
+ (and (:session-id opts) (:resume? opts)) (assoc :resumeSessionId (:session-id opts))
219
+ (:reasoning opts) (assoc :reasoningEffort (:reasoning opts))
220
+ (or (= harness-kw :gemini) (gemini-alias? harness-kw))
221
+ (assoc :debugRawEvents true))
222
+ (add-extra-args harness-kw opts)
223
+ json/generate-string)]
224
+ (process/sh ["agent-cli" "run" "--input" "-"]
225
+ {:in input :out :string :err :string})))
226
+
191
227
  ;; =============================================================================
192
228
  ;; Session Management
193
229
  ;; =============================================================================
@@ -207,17 +243,14 @@
207
243
  ;; =============================================================================
208
244
 
209
245
  (defn parse-output
210
- "Parse agent output. For :ndjson harnesses, extracts session-id and text.
211
- For :plain, returns output as-is.
246
+ "Parse unified JSONL output from `agent-cli run`.
212
247
  Returns {:output string, :session-id string}."
213
248
  [harness-kw raw-output session-id]
214
- (let [{:keys [output]} (get-config harness-kw)]
215
- (if (= output :ndjson)
216
- (let [parsed (parse-ndjson-output raw-output)]
217
- {:output (or (:text parsed) raw-output)
218
- :session-id (or (:session-id parsed) session-id)})
219
- {:output raw-output
220
- :session-id session-id})))
249
+ (let [parsed (parse-unified-jsonl-output raw-output)]
250
+ {:output (or (:text parsed) raw-output)
251
+ :session-id (or (:session-id parsed) session-id)
252
+ :warning (:warning parsed)
253
+ :raw-snippet (:raw-snippet parsed)}))
221
254
 
222
255
  ;; =============================================================================
223
256
  ;; Probe / Health Check — delegates to agent-cli check
@@ -105,7 +105,7 @@
105
105
  "Write a review log for one cycle of a worker.
106
106
  Contains: verdict, round number, full reviewer output, diff file list."
107
107
  [swarm-id worker-id cycle round
108
- {:keys [verdict output diff-files]}]
108
+ {:keys [verdict output diff-files duration-ms]}]
109
109
  (ensure-run-dirs! swarm-id)
110
110
  (let [filename (format "%s-c%d-r%d.json" worker-id cycle round)]
111
111
  (write-json! (str (reviews-dir swarm-id) "/" filename)
@@ -115,6 +115,7 @@
115
115
  :verdict (name verdict)
116
116
  :timestamp (str (java.time.Instant/now))
117
117
  :output output
118
+ :duration-ms (or duration-ms 0)
118
119
  :diff-files (vec diff-files)})))
119
120
 
120
121
  ;; =============================================================================
@@ -128,7 +129,7 @@
128
129
  Written at cycle end so dashboards can track progress in real-time."
129
130
  [swarm-id worker-id cycle
130
131
  {:keys [run outcome duration-ms claimed-task-ids recycled-tasks
131
- error-snippet review-rounds session-id]}]
132
+ error-snippet review-rounds session-id timing-ms]}]
132
133
  (when swarm-id
133
134
  (let [filename (format "%s-c%d.json" worker-id cycle)]
134
135
  (write-json! (str (cycles-dir swarm-id) "/" filename)
@@ -142,7 +143,8 @@
142
143
  :recycled-tasks (or recycled-tasks [])
143
144
  :error-snippet error-snippet
144
145
  :review-rounds (or review-rounds 0)
145
- :session-id session-id}))))
146
+ :session-id session-id
147
+ :timing-ms timing-ms}))))
146
148
 
147
149
  ;; =============================================================================
148
150
  ;; Read helpers (for cmd-status, dashboards)
@@ -210,6 +210,12 @@
210
210
  (unclaim-task! task))
211
211
  (mapv :id to-recycle)))
212
212
 
213
+ (defn recycle-all-current!
214
+ "Move every task from current/ back to pending/.
215
+ Returns vector of recycled task IDs."
216
+ []
217
+ (recycle-tasks! (current-task-ids)))
218
+
213
219
  (defn all-complete?
214
220
  "True if no pending or current tasks"
215
221
  []