Stream sub-agent chat to avoid TUI stalls
ober
55cf7187dfae43b4b878018f543c69250c9aaf57
--- a/src/jcode/core/agent.ss +++ b/src/jcode/core/agent.ss @@ -1476,6 +1476,7 @@ Be concise. Prefer edit over write for modifying existing files. (let ((err-port (current-error-port)) (log-lvl (current-log-level)) (tool-cb (current-tool-cb)) + (stream-abort (current-stream-abort?)) (mcp-cb (current-mcp-call-cb))) (let ((threads (map (lambda (tc) (spawn (lambda () @@ -1483,6 +1484,7 @@ Be concise. Prefer edit over write for modifying existing files. ((current-error-port err-port) (current-log-level log-lvl) (current-tool-cb tool-cb) + (current-stream-abort? stream-abort) (current-mcp-call-cb mcp-cb)) (execute-single-tool tc))))) tool-calls))) @@ -1623,14 +1625,57 @@ Be concise. Prefer edit over write for modifying existing files. (agent-chat-loop-stream provider new-msgs tools (+ round 1))))))))) (def (agent-chat-messages messages) - "Run the non-streaming chat loop over an explicit MESSAGES list (system + - history already built by the caller). Returns (values final-content + "Run the chat loop over an explicit MESSAGES list (system + history already + built by the caller). Returns (values final-content final-messages) so callers — the resumable task tool — can persist the transcript and continue the same sub-agent conversation later." (let ((provider (get-current-provider)) (tools (get-tool-schemas))) (parameterize ((forge-breaker-state (make-forge-breaker-state))) - (agent-chat-loop-track provider messages tools 0)))) + (agent-chat-loop-track-stream provider messages tools 0)))) + +(def (agent-chat-loop-track-stream provider messages tools round) + ;; Task/sub-agent conversations are not rendered token-by-token, but they + ;; still need streaming transport. A non-streaming local MLX read can block + ;; in the kernel long enough to pin the TUI and make ESC ineffective. + (let-values (((content tool-calls usage) + (stream-chat-with-expert provider messages tools + (lambda (_tok) (void))))) + (let* ((hermes-tcs (if (and (null? tool-calls) (not (string=? content ""))) + (try-parse-hermes-tool-calls content) + '())) + (effective-tcs (if (null? tool-calls) hermes-tcs tool-calls)) + (effective-content + (cond + ((not (null? hermes-tcs)) + (let ((clean (strip-hermes-blocks content))) + (if (string=? clean "") #f clean))) + (else (if (string=? content "") #f content))))) + (cond + ((null? effective-tcs) + (let ((final (make-assistant-message effective-content #f))) + (values (or effective-content "") + (append messages (list final))))) + ((>= round *max-tool-rounds*) + (let* ((response (make-assistant-message effective-content effective-tcs)) + (results (execute-tool-calls effective-tcs)) + (new-messages (append messages (list response) results))) + (let-values (((fc _tc _u) + (stream-chat-with-expert provider new-messages '() + (lambda (_tok) (void))))) + (let ((final (make-assistant-message (if (string=? fc "") #f fc) #f))) + (values (or fc "") + (append new-messages (list final))))))) + ((forge-no-progress? effective-tcs) + (log-warn logger "no-progress-break" `((round . ,round))) + (let ((final (make-assistant-message forge-no-progress-message #f))) + (values forge-no-progress-message + (append messages (list final))))) + (else + (let* ((response (make-assistant-message effective-content effective-tcs)) + (results (execute-tool-calls effective-tcs)) + (new-messages (append messages (list response) results))) + (agent-chat-loop-track-stream provider new-messages tools (+ round 1)))))))) (def (agent-chat-loop-track provider messages tools round) (let ((response (chat-with-expert provider messages tools)))