|
1 | | -(ns eca.features.tools.todo |
| 1 | +(ns eca.features.tools.task |
2 | 2 | (:require |
3 | 3 | [clojure.string :as str] |
4 | 4 | [eca.features.tools.util :as tools.util])) |
|
7 | 7 |
|
8 | 8 | ;; --- Helpers --- |
9 | 9 |
|
10 | | -(def ^:private empty-todo {:next-id 1 :active-summary nil :tasks []}) |
| 10 | +(def ^:private empty-task {:next-id 1 :active-summary nil :tasks []}) |
11 | 11 | (def ^:private valid-priorities #{:high :medium :low}) |
12 | 12 |
|
13 | 13 | (defn ^:private error [msg] |
|
32 | 32 |
|
33 | 33 | ;; --- State management --- |
34 | 34 |
|
35 | | -(defn get-todo |
36 | | - "Get TODO state for the chat." |
| 35 | +(defn get-task |
| 36 | + "Get task list state for the chat." |
37 | 37 | [db chat-id] |
38 | | - (get-in db [:chats chat-id :todo] empty-todo)) |
| 38 | + (get-in db [:chats chat-id :task] empty-task)) |
39 | 39 |
|
40 | | -(defn ^:private mutate-todo! |
41 | | - "Atomically update TODO for a chat via compare-and-set loop. |
| 40 | +(defn ^:private mutate-task! |
| 41 | + "Atomically update task list for a chat via compare-and-set loop. |
42 | 42 | `mutate-fn` receives current state, returns {:state ...} or {:error true ...}. |
43 | 43 | Extra keys in the result map are preserved." |
44 | 44 | [db* chat-id mutate-fn] |
45 | 45 | (loop [] |
46 | 46 | (let [db @db* |
47 | | - state (get-todo db chat-id) |
| 47 | + state (get-task db chat-id) |
48 | 48 | result (mutate-fn state)] |
49 | 49 | (if (:error result) |
50 | 50 | result |
51 | | - (let [new-db (assoc-in db [:chats chat-id :todo] (:state result))] |
| 51 | + (let [new-db (assoc-in db [:chats chat-id :task] (:state result))] |
52 | 52 | (if (compare-and-set! db* db new-db) |
53 | 53 | result |
54 | 54 | (recur))))))) |
|
226 | 226 | (str/join "\n" (map read-task-line-full tasks)) |
227 | 227 | "(none)")))) |
228 | 228 |
|
229 | | -(defn ^:private todo-details |
| 229 | +(defn ^:private task-details |
230 | 230 | "Structured data for client rendering." |
231 | 231 | [state] |
232 | 232 | (let [{:keys [tasks active-summary]} state |
233 | 233 | tasks-by-id (task-index tasks) |
234 | 234 | {:keys [done in-progress pending]} (status-counts tasks)] |
235 | | - (cond-> {:type :todo |
| 235 | + (cond-> {:type :task |
236 | 236 | :in-progress-task-ids (mapv :id (filter #(= :in-progress (:status %)) tasks)) |
237 | 237 | :tasks (mapv (fn [{:keys [id subject description status priority blocked-by]}] |
238 | 238 | (cond-> {:id id |
|
247 | 247 | active-summary (assoc :active-summary active-summary)))) |
248 | 248 |
|
249 | 249 | (defn ^:private success [state text] |
250 | | - (assoc (tools.util/single-text-content text) :details (todo-details state))) |
| 250 | + (assoc (tools.util/single-text-content text) :details (task-details state))) |
251 | 251 |
|
252 | 252 | (defn ^:private format-tasks-list [tasks] |
253 | 253 | (str/join "\n" (map read-task-line-short tasks))) |
254 | 254 |
|
255 | | -(defmethod tools.util/tool-call-details-after-invocation :todo |
| 255 | +(defmethod tools.util/tool-call-details-after-invocation :task |
256 | 256 | [_name _arguments before-details result _ctx] |
257 | 257 | (or (:details result) before-details)) |
258 | 258 |
|
259 | 259 | ;; --- Operations --- |
260 | 260 |
|
261 | 261 | (defn ^:private op-read [_arguments {:keys [db chat-id]}] |
262 | | - (let [state (get-todo db chat-id)] |
| 262 | + (let [state (get-task db chat-id)] |
263 | 263 | (success state (read-text state)))) |
264 | 264 |
|
265 | 265 | (defn ^:private op-plan [{:strs [tasks]} {:keys [db* chat-id]}] |
266 | 266 | (or (when-not (and (sequential? tasks) (seq tasks)) |
267 | 267 | (error "plan requires 'tasks' (non-empty array)")) |
268 | | - (let [result (mutate-todo! db* chat-id |
| 268 | + (let [result (mutate-task! db* chat-id |
269 | 269 | (fn [_state] |
270 | | - (let [built (build-tasks empty-todo tasks)] |
| 270 | + (let [built (build-tasks empty-task tasks)] |
271 | 271 | (if (:error built) |
272 | 272 | built |
273 | 273 | (or (detect-cycle (:tasks built)) |
274 | | - {:state (assoc empty-todo |
| 274 | + {:state (assoc empty-task |
275 | 275 | :tasks (:tasks built) |
276 | 276 | :next-id (inc (count (:tasks built))))})))))] |
277 | 277 | (if (:error result) |
278 | 278 | result |
279 | 279 | (success (:state result) |
280 | | - (str "TODO created with " (count (get-in result [:state :tasks])) " tasks")))))) |
| 280 | + (str "Task list created with " (count (get-in result [:state :tasks])) " tasks")))))) |
281 | 281 |
|
282 | 282 | (defn ^:private op-add [{:strs [tasks task] :as _arguments} {:keys [db* chat-id]}] |
283 | | - (let [result (mutate-todo! db* chat-id |
| 283 | + (let [result (mutate-task! db* chat-id |
284 | 284 | (fn [state] |
285 | 285 | (cond |
286 | 286 | tasks |
|
317 | 317 | (def ^:private updatable-keys #{"subject" "description" "priority" "blocked_by"}) |
318 | 318 |
|
319 | 319 | (defn ^:private op-update [{:strs [id task]} {:keys [db* chat-id]}] |
320 | | - (let [result (mutate-todo! db* chat-id |
| 320 | + (let [result (mutate-task! db* chat-id |
321 | 321 | (fn [state] |
322 | 322 | (let [[id err] (resolve-id state id)] |
323 | 323 | (or err |
|
372 | 372 |
|
373 | 373 | (defn ^:private op-start [{:strs [ids active_summary]} {:keys [db* chat-id]}] |
374 | 374 | (or (require-nonblank "active_summary" active_summary) |
375 | | - (let [result (mutate-todo! db* chat-id |
| 375 | + (let [result (mutate-task! db* chat-id |
376 | 376 | (fn [state] |
377 | 377 | (let [tasks-by-id (task-index (:tasks state)) |
378 | 378 | [ids err] (resolve-ids state ids)] |
|
406 | 406 | state)) |
407 | 407 |
|
408 | 408 | (defn ^:private op-complete [{:strs [ids]} {:keys [db* chat-id]}] |
409 | | - (let [result (mutate-todo! db* chat-id |
| 409 | + (let [result (mutate-task! db* chat-id |
410 | 410 | (fn [state] |
411 | 411 | (let [tasks-by-id (task-index (:tasks state)) |
412 | 412 | [ids err] (resolve-ids state ids)] |
|
444 | 444 | (format "\nUnblocked: %s" (str/join ", " unblocked))))))))) |
445 | 445 |
|
446 | 446 | (defn ^:private op-delete [{:strs [ids]} {:keys [db* chat-id]}] |
447 | | - (let [result (mutate-todo! db* chat-id |
| 447 | + (let [result (mutate-task! db* chat-id |
448 | 448 | (fn [state] |
449 | 449 | (let [[ids err] (resolve-ids state ids)] |
450 | 450 | (or err |
|
465 | 465 | (format-tasks-list (:deleted result))))))) |
466 | 466 |
|
467 | 467 | (defn ^:private op-clear [_arguments {:keys [db* chat-id]}] |
468 | | - (let [result (mutate-todo! db* chat-id (fn [_] {:state empty-todo}))] |
| 468 | + (let [result (mutate-task! db* chat-id (fn [_] {:state empty-task}))] |
469 | 469 | (if (:error result) |
470 | 470 | result |
471 | | - (success (:state result) "TODO cleared")))) |
| 471 | + (success (:state result) "Task list cleared")))) |
472 | 472 |
|
473 | 473 | ;; --- Dispatch --- |
474 | 474 |
|
|
482 | 482 | "delete" op-delete |
483 | 483 | "clear" op-clear}) |
484 | 484 |
|
485 | | -(defn ^:private execute-todo [arguments ctx] |
| 485 | +(defn ^:private execute-task [arguments ctx] |
486 | 486 | (let [op (get arguments "op") |
487 | 487 | handler (get ops op)] |
488 | 488 | (if handler |
489 | 489 | (handler arguments ctx) |
490 | 490 | (error (str "Unknown operation: " op))))) |
491 | 491 |
|
492 | 492 | (def definitions |
493 | | - {"todo" |
494 | | - {:description (tools.util/read-tool-description "todo") |
| 493 | + {"task" |
| 494 | + {:description (tools.util/read-tool-description "task") |
495 | 495 | :parameters {:type "object" |
496 | 496 | :properties {:op {:type "string" |
497 | 497 | :enum ["read" "plan" "add" "update" "start" "complete" "delete" "clear"] |
|
520 | 520 | :blocked_by {:type "array" :items {:type "integer"}}} |
521 | 521 | :required ["subject" "description"]}}} |
522 | 522 | :required ["op"]} |
523 | | - :handler execute-todo}}) |
| 523 | + :handler execute-task}}) |
0 commit comments