|
100 | 100 | "Expected state machine to contain [:execution-approved :stop-requested] transition") |
101 | 101 | (is (contains? state-machine [:executing :stop-requested]) |
102 | 102 | "Expected state machine to contain [:executing :stop-requested] transition") |
| 103 | + (is (contains? state-machine [:stopping :stop-requested]) |
| 104 | + "Expected state machine to contain [:stopping :stop-requested] transition") |
| 105 | + (is (contains? state-machine [:cleanup :stop-requested]) |
| 106 | + "Expected state machine to contain [:cleanup :stop-requested] transition") |
103 | 107 |
|
104 | | - ;; Note: :rejected, :completed and :stopped are terminal states, so no stop transitions. |
105 | | - (is (not (contains? state-machine [:stopped :stop-requested])) |
106 | | - "Expected :rejected state to not have stop transition defined, since it is a terminal state") |
| 108 | + ;; Note: :rejected and :completed are terminal states, so no stop transitions. |
107 | 109 | (is (not (contains? state-machine [:rejected :stop-requested])) |
108 | 110 | "Expected :rejected state to not have stop transition defined, since it is a terminal state") |
109 | 111 | (is (not (contains? state-machine [:completed :stop-requested])) |
|
261 | 263 | (let [result (#'f.chat/transition-tool-call! db* chat-ctx tool-call-id :tool-run run-event-data)] |
262 | 264 |
|
263 | 265 | (is (match? {:status :check-approval |
264 | | - :actions [:init-arguments :init-approval-promise :send-toolCallRun]} |
| 266 | + :actions [:init-arguments :init-approval-promise :init-future-cleanup-promise :send-toolCallRun]} |
265 | 267 | result) |
266 | 268 | "Expected next state to be :check-approval with actions of :init-approval-promise and :send-toolCallRun") |
267 | 269 |
|
|
316 | 318 | ;; Step 2: :preparing -> :check-approval |
317 | 319 | (let [result (#'f.chat/transition-tool-call! db* chat-ctx tool-call-id :tool-run run-event-data)] |
318 | 320 | (is (match? {:status :check-approval |
319 | | - :actions [:init-arguments :init-approval-promise :send-toolCallRun]} |
| 321 | + :actions [:init-arguments :init-approval-promise :init-future-cleanup-promise :send-toolCallRun]} |
320 | 322 | result) |
321 | 323 | "Expected transition to :check-approval with init promise and send run actions") |
322 | 324 |
|
|
451 | 453 | (let [result (#'f.chat/transition-tool-call! db* chat-ctx tool-call-id :execution-start |
452 | 454 | {:name "list_files" :origin "filesystem" :arguments {:path "/tmp"}})] |
453 | 455 | (is (match? {:status :executing |
454 | | - :actions [:set-start-time :set-call-future :send-toolCallRunning :send-progress]} |
| 456 | + :actions [:set-start-time :add-future :send-toolCallRunning :send-progress]} |
455 | 457 | result) |
456 | 458 | "Expected transition to :executing status with no additional actions") |
457 | 459 |
|
458 | 460 | (let [tool-state (#'f.chat/get-tool-call-state @db* chat-id tool-call-id)] |
459 | 461 | (is (= :executing (:status tool-state)) |
460 | 462 | "Expected tool call state to be in :executing status")))) |
461 | 463 |
|
462 | | - ;; Step 2: :executing -> :completed |
463 | | - (testing ":executing -> :completed transition" |
| 464 | + ;; Step 2: :executing -> :cleanup |
| 465 | + (testing ":executing -> :cleanup transition" |
464 | 466 | (let [result-data {:outputs "file1.txt\nfile2.txt\nfile3.txt" |
465 | 467 | :error nil |
466 | 468 | :name "list_files" |
467 | 469 | :origin "filesystem" |
468 | 470 | :arguments {:path "/tmp"}} |
469 | 471 | result (#'f.chat/transition-tool-call! db* chat-ctx tool-call-id :execution-end result-data)] |
470 | 472 |
|
471 | | - (is (match? {:status :completed |
472 | | - :actions [:send-toolCalled :log-metrics :send-progress]} |
| 473 | + (is (match? {:status :cleanup |
| 474 | + :actions [:deliver-future-cleanup-completed :send-toolCalled :log-metrics :send-progress]} |
473 | 475 | result) |
474 | | - "Expected transition to :completed with send toolCalled and record metrics actions") |
| 476 | + "Expected transition to :cleanup with send toolCalled and record metrics actions") |
475 | 477 |
|
476 | 478 | (let [tool-state (#'f.chat/get-tool-call-state @db* chat-id tool-call-id)] |
477 | | - (is (= :completed (:status tool-state)) |
478 | | - "Expected tool call state to be in :completed status")) |
| 479 | + (is (= :cleanup (:status tool-state)) |
| 480 | + "Expected tool call state to be in :cleanup status")) |
479 | 481 |
|
480 | 482 | (let [messages (h/messages) |
481 | 483 | chat-messages (:chat-content-received messages) |
|
501 | 503 | chat-id "test-chat" |
502 | 504 | chat-ctx {:chat-id chat-id :request-id "req-1" :messenger (h/messenger)}] |
503 | 505 |
|
504 | | - ;; Test :initial -> :stopped |
505 | | - (testing ":initial -> :stopped" |
| 506 | + ;; Test :initial -> :cleanup |
| 507 | + (testing ":initial -> :cleanup" |
506 | 508 | (let [result (#'f.chat/transition-tool-call! db* chat-ctx "tool-initial" :stop-requested)] |
507 | | - (is (match? {:status :stopped :actions []} result) |
508 | | - "Expected transition from :initial to :stopped with no actions"))) |
| 509 | + (is (match? {:status :cleanup :actions []} result) |
| 510 | + "Expected transition from :initial to :cleanup with no actions"))) |
509 | 511 |
|
510 | | - ;; Test :preparing -> :stopped (already covered in transition-tool-call-stop-transitions-test) |
511 | | - ;; Test :check-approval -> :stopped |
512 | | - (testing ":check-approval -> :stopped" |
| 512 | + ;; Test :preparing -> :cleanup (already covered in transition-tool-call-stop-transitions-test) |
| 513 | + ;; Test :check-approval -> :cleanup |
| 514 | + (testing ":check-approval -> :cleanup" |
513 | 515 | (let [approved?* (promise)] |
514 | 516 | (#'f.chat/transition-tool-call! db* chat-ctx "tool-check" :tool-prepare |
515 | 517 | {:name "test" :origin "test" :arguments-text "{}"}) |
|
522 | 524 | result) |
523 | 525 | "Expected transition from :check-approval to :rejected with relevant actions")))) |
524 | 526 |
|
525 | | - ;; Test :executing -> :stopping -> :stopped |
526 | | - (testing ":executing -> :stopping -> :stopped" |
| 527 | + ;; Test :executing -> :stopping -> :cleanup |
| 528 | + (testing ":executing -> :stopping -> :cleanup" |
527 | 529 | (let [approved?* (promise)] |
528 | 530 | (#'f.chat/transition-tool-call! db* chat-ctx "tool-executing" :tool-prepare |
529 | 531 | {:name "test" :origin "test" :arguments-text "{}"}) |
|
535 | 537 |
|
536 | 538 | (let [result (#'f.chat/transition-tool-call! db* chat-ctx "tool-executing" :stop-requested)] |
537 | 539 | (is (match? {:status :stopping |
538 | | - :actions []} |
| 540 | + :actions [:cancel-future]} |
539 | 541 | result) |
540 | 542 | "Expected transition from :executing to :stopping with relevant actions")) |
541 | 543 | (let [result (#'f.chat/transition-tool-call! db* chat-ctx "tool-executing" :stop-attempted)] |
542 | | - (is (match? {:status :stopped |
543 | | - :actions [:send-toolCallRejected]} |
| 544 | + (is (match? {:status :cleanup |
| 545 | + :actions [:deliver-future-cleanup-completed :send-toolCallRejected]} |
544 | 546 | result) |
545 | | - "Expected transition from :stopping to :stopped with relevant actions")))) |
| 547 | + "Expected transition from :stopping to :cleanup with relevant actions")))) |
546 | 548 |
|
547 | 549 |
|
548 | | - ;; Test :completed -> :stopped (should be no-op or error) |
549 | | - (testing ":completed -> :stopped (should handle gracefully)" |
| 550 | + ;; Test :cleanup -> :cleanup (should be no-op or error) |
| 551 | + (testing ":cleanup -> :cleanup (should handle gracefully)" |
550 | 552 | (let [approved?* (promise)] |
551 | 553 | (#'f.chat/transition-tool-call! db* chat-ctx "tool-completed" :tool-prepare |
552 | 554 | {:name "test" :origin "test" :arguments-text "{}"}) |
|
558 | 560 | (#'f.chat/transition-tool-call! db* chat-ctx "tool-completed" :execution-end |
559 | 561 | {:outputs "success" :error nil :name "test" :origin "test" :arguments {}}) |
560 | 562 |
|
561 | | - ;; Now try to stop a completed tool call |
562 | | - (is (thrown-with-msg? |
563 | | - clojure.lang.ExceptionInfo |
564 | | - #"Invalid state transition" |
565 | | - (#'f.chat/transition-tool-call! db* chat-ctx "tool-completed" :stop-requested)) |
566 | | - "Expected exception as completed tool calls cannot be stopped"))) |
| 563 | + ;; Now try to stop a tool call in :cleanup |
| 564 | + (let [result (#'f.chat/transition-tool-call! db* chat-ctx "tool-completed" :stop-requested)] |
| 565 | + (is (match? {:status :cleanup |
| 566 | + :actions []} |
| 567 | + result) |
| 568 | + "Expected a transition from :cleanup on :stop-requested to be a nop")))) |
567 | 569 |
|
568 | | - ;; Test :rejected -> :stopped |
569 | | - (testing ":rejected -> :stopped (should handle gracefully)" |
| 570 | + ;; Test :rejected -> :cleanup |
| 571 | + (testing ":rejected -> :cleanup (should handle gracefully)" |
570 | 572 | (let [approved?* (promise)] |
571 | 573 | (#'f.chat/transition-tool-call! db* chat-ctx "tool-rejected" :tool-prepare |
572 | 574 | {:name "test" :origin "test" :arguments-text "{}"}) |
|
591 | 593 | chat-id "test-chat" |
592 | 594 | chat-ctx {:chat-id chat-id :request-id "req-1" :messenger (h/messenger)}] |
593 | 595 |
|
594 | | - (testing ":preparing -> :stopped" |
| 596 | + (testing ":preparing -> :cleanup" |
595 | 597 | (#'f.chat/transition-tool-call! db* chat-ctx "tool-1" :tool-prepare |
596 | 598 | {:name "test" :origin "test" :arguments-text "{}"}) |
597 | 599 |
|
598 | 600 | (let [result (#'f.chat/transition-tool-call! db* chat-ctx "tool-1" :stop-requested)] |
599 | | - (is (match? {:status :stopped |
| 601 | + (is (match? {:status :cleanup |
600 | 602 | :actions [:set-decision-reason :send-toolCallRejected]} |
601 | 603 | result) |
602 | | - "Expected transition to :stopped with send toolCallRejected action") |
603 | | - (is (= :stopped (:status (#'f.chat/get-tool-call-state @db* chat-id "tool-1"))) |
604 | | - "Expected tool call state to be in :stopped status"))) |
| 604 | + "Expected transition to :cleanup with send toolCallRejected action") |
| 605 | + (is (= :cleanup (:status (#'f.chat/get-tool-call-state @db* chat-id "tool-1"))) |
| 606 | + "Expected tool call state to be in :cleanup status"))) |
605 | 607 |
|
606 | 608 | (testing ":waiting-approval -> :rejected" |
607 | 609 | (let [approved?* (promise)] |
|
622 | 624 | (is (= false (deref approved?* 100 :timeout)) |
623 | 625 | "Expected promise to be delivered with false value")))) |
624 | 626 |
|
625 | | - (testing ":execution-approved -> :stopped" |
| 627 | + (testing ":execution-approved -> :cleanup" |
626 | 628 | (let [approved?* (promise)] |
627 | 629 | (#'f.chat/transition-tool-call! db* chat-ctx "tool-3" :tool-prepare |
628 | 630 | {:name "test" :origin "test" :arguments-text "{}"}) |
|
631 | 633 | (#'f.chat/transition-tool-call! db* chat-ctx "tool-3" :approval-allow) |
632 | 634 |
|
633 | 635 | (let [result (#'f.chat/transition-tool-call! db* chat-ctx "tool-3" :stop-requested)] |
634 | | - (is (match? {:status :stopped |
| 636 | + (is (match? {:status :cleanup |
635 | 637 | :actions [:send-toolCallRejected]} |
636 | 638 | result) |
637 | | - "Expected transition to :stopped with send toolCallRejected action") |
638 | | - (is (= :stopped (:status (#'f.chat/get-tool-call-state @db* chat-id "tool-3"))) |
639 | | - "Expected tool call state to be in :stopped status"))))))) |
| 639 | + "Expected transition to :cleanup with send toolCallRejected action") |
| 640 | + (is (= :cleanup (:status (#'f.chat/get-tool-call-state @db* chat-id "tool-3"))) |
| 641 | + "Expected tool call state to be in :cleanup status"))))))) |
640 | 642 |
|
641 | 643 | (deftest test-stop-prompt-messages |
642 | 644 | ;; Test what messages are sent when stop-prompt is called |
|
829 | 831 |
|
830 | 832 | (deftest transition-tool-call-stop-during-execution-test |
831 | 833 | ;; Test stopping a tool call during execution |
832 | | - (testing ":executing -> :stopping -> :stopped transition" |
| 834 | + (testing ":executing -> :stopping -> :cleanup transition" |
833 | 835 | (h/reset-components!) |
834 | 836 | (let [db* (h/db*) |
835 | 837 | chat-id "test-chat" |
|
858 | 860 | result (#'f.chat/transition-tool-call! db* chat-ctx tool-call-id :stop-attempted)] |
859 | 861 | (is (= :stopping (:status tool-state)) |
860 | 862 | "Expected tool call state to be in :stopping status") |
861 | | - (is (= :stopped (:status result)) |
862 | | - "Expected tool call state to be :stopped after :stop-requested")) |
| 863 | + (is (= :cleanup (:status result)) |
| 864 | + "Expected tool call state to be :cleanup after :stop-requested")) |
863 | 865 | ))) |
864 | 866 |
|
865 | 867 | (deftest transition-tool-call-nonexistent-tool-call-operations-test |
|
896 | 898 | (#'f.chat/transition-tool-call! db* chat-ctx tool-call-id :execution-start |
897 | 899 | {:name "test" :server "eca" :origin "test" :arguments {}}) |
898 | 900 |
|
899 | | - (testing ":executing -> :completed with error" |
| 901 | + (testing ":executing -> :cleanup with error" |
900 | 902 | (let [error-result {:outputs nil |
901 | 903 | :error "File not found: /nonexistent/path" |
902 | 904 | :name "test" |
|
905 | 907 | :arguments {}} |
906 | 908 | result (#'f.chat/transition-tool-call! db* chat-ctx tool-call-id :execution-end error-result)] |
907 | 909 |
|
908 | | - (is (match? {:status :completed |
909 | | - :actions [:send-toolCalled :log-metrics :send-progress]} |
| 910 | + (is (match? {:status :cleanup |
| 911 | + :actions [:deliver-future-cleanup-completed :send-toolCalled :log-metrics :send-progress]} |
910 | 912 | result) |
911 | | - "Expected transition to :completed with send toolCalled and record metrics actions") |
| 913 | + "Expected transition to :cleanup with send toolCalled and record metrics actions") |
912 | 914 |
|
913 | 915 | (let [messages (h/messages) |
914 | 916 | chat-messages (:chat-content-received messages) |
|
0 commit comments