Skip to content

Commit d119c43

Browse files
committed
Auto-clear completed task lists on new prompt
When a new prompt is sent and all tasks in the task list are done, automatically clear the list so the model starts fresh. The cleared state is sent to the client as a synthetic toolCalled event, reusing the existing task-details rendering path. No protocol or client changes.
1 parent 84c313c commit d119c43

4 files changed

Lines changed: 73 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Auto-clear completed task lists: when a new prompt is sent and all tasks are done, the list is automatically cleared.
6+
57
## 0.142.2
68

79
- `/context` now shows where auto-compaction triggers: a `🔲` marker on the threshold cell of the grid plus an `Auto-compaction at N%` line.

src/eca/features/chat.clj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
[eca.features.skills :as f.skills]
2222
[eca.features.tools :as f.tools]
2323
[eca.features.tools.mcp :as f.mcp]
24+
[eca.features.tools.task :as f.tools.task]
2425
[eca.llm-api :as llm-api]
2526
[eca.llm-providers.errors :as llm-providers.errors]
2627
[eca.llm-util :as llm-util]
@@ -1653,6 +1654,13 @@
16531654
_ (when (and seeded-default-trust? provided-chat-id)
16541655
(config/notify-fields-changed-only! {:chat {:select-trust true}} messenger db* chat-id))]
16551656
(logger/with-chat-context chat-id (:parent-chat-id base-chat-ctx)
1657+
(when-let [cleared-details (f.tools.task/auto-clear-completed! db* chat-id)]
1658+
(logger/info logger-tag "Auto-cleared completed task list" {:chat-id chat-id})
1659+
(lifecycle/send-content! base-chat-ctx :assistant
1660+
{:type :toolCalled
1661+
:server "eca"
1662+
:name "task"
1663+
:details cleared-details}))
16561664
(try
16571665
(prompt* params base-chat-ctx)
16581666
(catch Exception e

src/eca/features/tools/task.clj

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,23 @@
256256
[_name _arguments before-details result _ctx]
257257
(or (:details result) before-details))
258258

259+
;; --- Auto-clear ---
260+
261+
(defn ^:private all-tasks-done?
262+
"Returns true if the task list has tasks and all are :done."
263+
[state]
264+
(let [tasks (:tasks state)
265+
{:keys [done pending in-progress]} (status-counts tasks)]
266+
(and (pos? done) (zero? pending) (zero? in-progress))))
267+
268+
(defn auto-clear-completed!
269+
"Clear the task list when all tasks are done.
270+
Returns task-details of the cleared state if clearing occurred, nil otherwise."
271+
[db* chat-id]
272+
(when (all-tasks-done? (get-task @db* chat-id))
273+
(mutate-task! db* chat-id (fn [_] {:state empty-task}))
274+
(task-details empty-task)))
275+
259276
;; --- Operations ---
260277

261278
(defn ^:private op-read [_arguments {:keys [db chat-id]}]
@@ -502,7 +519,7 @@
502519
:items {:type "integer"}
503520
:description "Task IDs (required for start/complete/delete)"}
504521
:active_summary {:type "string"
505-
:description "Summary of what will be done in the current active session. Required for start operation."}
522+
:description "Summary of what will be done in the current active session. Required for start operation."}
506523
:task {:type "object"
507524
:description "Single task data (for add/update)"
508525
:properties {:subject {:type "string" :description "Task subject/title (required)"}

test/eca/features/tools/task_test.clj

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,48 @@
482482
result
483483
nil)]
484484
(is (= (:details result) details)))))
485+
486+
(deftest auto-clear-completed-test
487+
(testing "clears task list when all tasks are done"
488+
(let [db* (atom {:chats {"c1" {:task {:next-id 3
489+
:active-summary nil
490+
:tasks [{:id 1 :subject "T1" :description "D1" :status :done :priority :medium :blocked-by #{}}
491+
{:id 2 :subject "T2" :description "D2" :status :done :priority :medium :blocked-by #{}}]}}}})
492+
result (task/auto-clear-completed! db* "c1")]
493+
(is (some? result))
494+
(is (= :task (:type result)))
495+
(is (empty? (:tasks result)))
496+
(is (= {:next-id 1 :active-summary nil :tasks []}
497+
(task/get-task @db* "c1")))))
498+
499+
(testing "does not clear when tasks are still pending"
500+
(let [db* (atom {:chats {"c1" {:task {:next-id 3
501+
:active-summary nil
502+
:tasks [{:id 1 :subject "T1" :description "D1" :status :done :priority :medium :blocked-by #{}}
503+
{:id 2 :subject "T2" :description "D2" :status :pending :priority :medium :blocked-by #{}}]}}}})
504+
result (task/auto-clear-completed! db* "c1")]
505+
(is (nil? result))
506+
(is (= 2 (count (:tasks (task/get-task @db* "c1")))))))
507+
508+
(testing "does not clear when tasks are in progress"
509+
(let [db* (atom {:chats {"c1" {:task {:next-id 3
510+
:active-summary "working"
511+
:tasks [{:id 1 :subject "T1" :description "D1" :status :done :priority :medium :blocked-by #{}}
512+
{:id 2 :subject "T2" :description "D2" :status :in-progress :priority :medium :blocked-by #{}}]}}}})
513+
result (task/auto-clear-completed! db* "c1")]
514+
(is (nil? result))
515+
(is (= 2 (count (:tasks (task/get-task @db* "c1")))))))
516+
517+
(testing "does nothing when task list is empty"
518+
(let [db* (atom {:chats {"c1" {:task {:next-id 1 :active-summary nil :tasks []}}}})
519+
result (task/auto-clear-completed! db* "c1")]
520+
(is (nil? result))
521+
(is (= {:next-id 1 :active-summary nil :tasks []}
522+
(task/get-task @db* "c1")))))
523+
524+
(testing "does nothing when chat has no task list"
525+
(let [db* (atom {})
526+
result (task/auto-clear-completed! db* "c1")]
527+
(is (nil? result))
528+
(is (= {:next-id 1 :active-summary nil :tasks []}
529+
(task/get-task @db* "c1"))))))

0 commit comments

Comments
 (0)