Fix streaming, plugin loading, and auth; exclude internal tools from generated-code

ober

4fd5ef8e7abc161623f5dadb2ac498cf2acdf1ef

diff --git a/build-binary.ss b/build-binary.ss
index cd80d15..b07f6fc 100644
--- a/build-binary.ss
+++ b/build-binary.ss
@@ -220,6 +220,14 @@
                (generate-wpo-files #t))
   (compile-program "main-binary.ss"))
 
+;; Keep plugin-facing libraries visible at runtime by excluding from WPO.
+(for-each (lambda (m)
+            (let ((wpo (format "~a.wpo" m)))
+              (when (file-exists? wpo)
+                (printf "  Excluding ~a from WPO (plugin-visible)~n" m)
+                (delete-file wpo))))
+  '("lib/jcode/tool/registry"))
+
 ;; --- Step 2: Whole-program optimization ---
 (printf "[2/6] Running whole-program optimization...~n")
 (let ((missing (compile-whole-program "main-binary.wpo" "jcode-all.so")))
@@ -241,6 +249,7 @@
   (map (lambda (m) (format "~a/~a.so" jerboa-lib-dir m))
     '("jerboa/core"
       "jerboa/runtime"
+      "jerboa/prelude"
       "std/error"
       "std/format"
       "std/sort"
diff --git a/lib/jcode/core/config.sls b/lib/jcode/core/config.sls
index d294f80..631ffc8 100644
--- a/lib/jcode/core/config.sls
+++ b/lib/jcode/core/config.sls
@@ -56,8 +56,13 @@
          (hash-put! config "providers" providers)
          config))
   (def (merge-opencode-keys! providers)
-       (let ([auth-file (path-join (getenv "HOME") ".local" "share"
-                          "opencode" "auth.json")])
+       (let ([auth-file (let ([jcode-auth (path-join
+                                            (jcode-home)
+                                            "auth.json")])
+                          (if (file-exists? jcode-auth)
+                              jcode-auth
+                              (path-join (getenv "HOME") ".local" "share"
+                                "opencode" "auth.json")))])
          (when (file-exists? auth-file)
            (try (let ([auth (call-with-input-file
                               auth-file
diff --git a/lib/jcode/core/models.sls b/lib/jcode/core/models.sls
index 7d45899..3f9469c 100644
--- a/lib/jcode/core/models.sls
+++ b/lib/jcode/core/models.sls
@@ -60,15 +60,31 @@
           ("gemini-2.5-flash" . "Gemini 2.5 Flash")
           ("gemini-2.0-flash" . "Gemini 2.0 Flash")))
   (def openrouter-models
-       '(("anthropic/claude-sonnet-4" . "Claude Sonnet 4") ("anthropic/claude-opus-4" . "Claude Opus 4")
-          ("openai/gpt-4o" . "GPT-4o")
-          ("google/gemini-2.5-pro" . "Gemini 2.5 Pro")
-          ("google/gemini-2.5-flash" . "Gemini 2.5 Flash")
-          ("deepseek/deepseek-chat" . "DeepSeek V3")
-          ("deepseek/deepseek-r1" . "DeepSeek R1")
-          ("meta-llama/llama-4-maverick" . "Llama 4 Maverick")
-          ("meta-llama/llama-4-scout" . "Llama 4 Scout")
-          ("qwen/qwen3-235b-a22b" . "Qwen3 235B")))
+       '(("anthropic/claude-sonnet-4" . "Claude Sonnet 4")
+         ("anthropic/claude-opus-4" . "Claude Opus 4")
+         ("anthropic/claude-haiku-3.5" . "Claude 3.5 Haiku")
+         ("openai/gpt-4o" . "GPT-4o")
+         ("openai/gpt-4o-mini" . "GPT-4o Mini") ("openai/o3" . "o3")
+         ("openai/o3-mini" . "o3 Mini")
+         ("openai/o4-mini" . "o4 Mini")
+         ("google/gemini-2.5-pro" . "Gemini 2.5 Pro")
+         ("google/gemini-2.5-flash" . "Gemini 2.5 Flash")
+         ("google/gemini-2.0-flash" . "Gemini 2.0 Flash")
+         ("meta-llama/llama-4-maverick" . "Llama 4 Maverick")
+         ("meta-llama/llama-4-scout" . "Llama 4 Scout")
+         ("meta-llama/llama-3.3-70b-instruct" . "Llama 3.3 70B")
+         ("deepseek/deepseek-chat-v3-0324" . "DeepSeek V3 0324")
+         ("deepseek/deepseek-r1" . "DeepSeek R1")
+         ("qwen/qwen3-235b-a22b" . "Qwen3 235B")
+         ("qwen/qwen3-30b-a3b" . "Qwen3 30B")
+         ("mistralai/mistral-large" . "Mistral Large")
+         ("mistralai/mistral-medium" . "Mistral Medium")
+         ("mistralai/codestral" . "Codestral")
+         ("cohere/command-r-plus" . "Command R+")
+         ("x-ai/grok-3" . "Grok 3")
+         ("x-ai/grok-3-mini" . "Grok 3 Mini")
+         ("microsoft/phi-4" . "Phi-4")
+         ("nousresearch/hermes-3-llama-3.1-405b" . "Hermes 3 405B")))
   (def ollama-models
        '(("llama3.2" . "Llama 3.2") ("llama3.1" . "Llama 3.1")
           ("qwen2.5-coder" . "Qwen 2.5 Coder")
diff --git a/lib/jcode/core/plugin.sls b/lib/jcode/core/plugin.sls
index c6bb893..15d2d9a 100644
--- a/lib/jcode/core/plugin.sls
+++ b/lib/jcode/core/plugin.sls
@@ -37,9 +37,20 @@
                          (let ([entries (directory-list dir)])
                            (if (list? entries) entries '())))))
                 dirs))))
-  (def (load-plugin path)
-       "Load a single plugin file."
+  (def (ensure-library-dirs!)
+       "Ensure library-directories includes Jerboa lib dir for plugin imports."
+       (let ([jerboa-lib (path-join
+                           (or (getenv "JERBOA_HOME")
+                               (path-join (getenv "HOME") "mine/jerboa"))
+                           "lib")])
+         (when (file-directory? jerboa-lib)
+           (let ([entry (cons jerboa-lib jerboa-lib)])
+             (unless (member entry (library-directories))
+               (library-directories
+                 (cons entry (library-directories))))))))
+  (def (load-plugin path) "Load a single plugin file."
        (log-info logger "loading" `((path . ,path)))
+       (ensure-library-dirs!)
        (try (load path)
             (set! *loaded-plugins* (cons path *loaded-plugins*))
             (log-info logger "loaded" `((path . ,path))) #t
diff --git a/lib/jcode/mcp/client.sls b/lib/jcode/mcp/client.sls
index 8763e27..6f9092e 100644
--- a/lib/jcode/mcp/client.sls
+++ b/lib/jcode/mcp/client.sls
@@ -166,7 +166,7 @@
                    [schema (or (hash-get tool "inputSchema")
                                (make-hash-table))])
                (let ([jcode-name (string-append prefix name)])
-                 (register-tool!
+                 (register-internal-tool!
                    jcode-name
                    desc
                    schema
@@ -181,6 +181,7 @@
              '())))
   (def (init-mcp-tools)
        "Initialize all configured MCP servers."
+       (mcp-stop-all!)
        (let ([configs (load-mcp-config)])
          (for-each
            (lambda (pair)
diff --git a/lib/jcode/provider/provider.sls b/lib/jcode/provider/provider.sls
index b5e4c78..2170277 100644
--- a/lib/jcode/provider/provider.sls
+++ b/lib/jcode/provider/provider.sls
@@ -371,66 +371,123 @@
               [body (openai-stream-body provider messages tools)]
               [text-acc (open-output-string)]
               [tc-table (make-hash-table)])
-         (http-post-stream
-           url
-           headers
-           (json-object->string body)
-           (lambda (event-str)
-             (when event-str
-               (let* ([data (if (string-prefix? "data: " event-str)
-                                (substring
-                                  event-str
-                                  6
-                                  (string-length event-str))
-                                event-str)])
-                 (cond
-                   [(equal? data "[DONE]") (void)]
-                   [else
-                    (let ([json (guard (e [list #t #f])
-                                  (string->json-object data))])
-                      (when json
-                        (let* ([choices (hash-get json "choices")]
-                               [choice (and (pair? choices) (car choices))]
-                               [delta (and choice
-                                           (hash-get choice "delta"))])
-                          (when delta
-                            (let ([content (hash-get delta "content")])
-                              (when (and content
-                                         (not (eq? content (void))))
-                                (put-string text-acc content)
-                                (token-cb content)))
-                            (let ([tcs (hash-get delta "tool_calls")])
-                              (when (and tcs (list? tcs))
-                                (for-each
-                                  (lambda (tc)
-                                    (let* ([idx (or (hash-get tc "index")
-                                                    0)]
-                                           [acc (or (hash-get tc-table idx)
-                                                    (let ([a (make-hash-table)])
-                                                      (hash-put!
-                                                        tc-table
-                                                        idx
-                                                        a)
-                                                      a))]
-                                           [id (hash-get tc "id")]
-                                           [fn (hash-get tc "function")])
-                                      (when id (hash-put! acc "id" id))
-                                      (when fn
-                                        (let ([name (hash-get fn "name")]
-                                              [args (hash-get
-                                                      fn
-                                                      "arguments")])
-                                          (when name
-                                            (hash-put! acc "name" name))
-                                          (when args
-                                            (hash-put!
-                                              acc
-                                              "args"
-                                              (string-append
-                                                (or (hash-get acc "args")
-                                                    "")
-                                                args)))))))
-                                  tcs)))))))])))))
+         (let* ([body-json (json-object->string body)]
+                [dummy (log-info
+                         logger
+                         "stream-request"
+                         `((url . ,url)
+                            (body-len . ,(string-length body-json))))]
+                [http-status (http-post-stream
+                               url
+                               headers
+                               body-json
+                               (lambda (event-str)
+                                 (when event-str
+                                   (log-info
+                                     logger
+                                     "sse-event"
+                                     `((data
+                                         .
+                                         ,(if (> (string-length event-str)
+                                                 120)
+                                              (substring event-str 0 120)
+                                              event-str))))
+                                   (let* ([data (if (string-prefix?
+                                                      "data: "
+                                                      event-str)
+                                                    (substring
+                                                      event-str
+                                                      6
+                                                      (string-length
+                                                        event-str))
+                                                    event-str)])
+                                     (cond
+                                       [(equal? data "[DONE]") (void)]
+                                       [else
+                                        (let ([json (guard (e [list #t #f])
+                                                      (string->json-object
+                                                        data))])
+                                          (when json
+                                            (let* ([choices (hash-get
+                                                              json
+                                                              "choices")]
+                                                   [choice (and (pair?
+                                                                  choices)
+                                                                (car choices))]
+                                                   [delta (and choice
+                                                               (hash-get
+                                                                 choice
+                                                                 "delta"))])
+                                              (when delta
+                                                (let ([content (hash-get
+                                                                 delta
+                                                                 "content")])
+                                                  (when (and content
+                                                             (not (eq? content
+                                                                       (void))))
+                                                    (put-string
+                                                      text-acc
+                                                      content)
+                                                    (token-cb content)))
+                                                (let ([tcs (hash-get
+                                                             delta
+                                                             "tool_calls")])
+                                                  (when (and tcs
+                                                             (list? tcs))
+                                                    (for-each
+                                                      (lambda (tc)
+                                                        (let* ([idx (or (hash-get
+                                                                          tc
+                                                                          "index")
+                                                                        0)]
+                                                               [acc (or (hash-get
+                                                                          tc-table
+                                                                          idx)
+                                                                        (let ([a (make-hash-table)])
+                                                                          (hash-put!
+                                                                            tc-table
+                                                                            idx
+                                                                            a)
+                                                                          a))]
+                                                               [id (hash-get
+                                                                     tc
+                                                                     "id")]
+                                                               [fn (hash-get
+                                                                     tc
+                                                                     "function")])
+                                                          (when id
+                                                            (hash-put!
+                                                              acc
+                                                              "id"
+                                                              id))
+                                                          (when fn
+                                                            (let ([name (hash-get
+                                                                          fn
+                                                                          "name")]
+                                                                  [args (hash-get
+                                                                          fn
+                                                                          "arguments")])
+                                                              (when name
+                                                                (hash-put!
+                                                                  acc
+                                                                  "name"
+                                                                  name))
+                                                              (when args
+                                                                (hash-put!
+                                                                  acc
+                                                                  "args"
+                                                                  (string-append
+                                                                    (or (hash-get
+                                                                          acc
+                                                                          "args")
+                                                                        "")
+                                                                    args)))))))
+                                                      tcs)))))))])))))])
+           (unless (= http-status 200)
+             (log-error
+               logger
+               "stream-http-error"
+               `((status . ,http-status) (url . ,url)))))
          (let* ([content (get-output-string text-acc)]
                 [indices (sort < (hash-keys tc-table))]
                 [tool-calls (map (lambda (idx)
@@ -441,6 +498,11 @@
                                        (or (hash-get acc "name") "unknown")
                                        (or (hash-get acc "args") "{}"))))
                                  indices)])
+           (log-info
+             logger
+             "stream-result"
+             `((content-len . ,(string-length content))
+                (tool-calls . ,(length tool-calls))))
            (values content tool-calls))))
   (def (anthropic-stream-headers provider)
        `(("Content-Type" . "application/json")
@@ -463,79 +525,128 @@
               [text-acc (open-output-string)]
               [tu-table (make-hash-table)]
               [current-idx (make-parameter #f)])
-         (http-post-stream
-           url
-           headers
-           (json-object->string body)
-           (lambda (event-str)
-             (when event-str
-               (let* ([lines (string-split event-str #\newline)]
-                      [event-type #f]
-                      [data-str #f])
-                 (for-each
-                   (lambda (line)
-                     (cond
-                       [(string-prefix? "event: " line)
-                        (set! event-type
-                          (substring line 7 (string-length line)))]
-                       [(string-prefix? "data: " line)
-                        (set! data-str
-                          (substring line 6 (string-length line)))]))
-                   lines)
-                 (when (and event-type data-str)
-                   (let ([json (guard (e [list #t #f])
-                                 (string->json-object data-str))])
-                     (when json
-                       (cond
-                         [(equal? event-type "content_block_delta")
-                          (let ([delta (hash-get json "delta")])
-                            (when delta
-                              (let ([dtype (hash-get delta "type")])
-                                (cond
-                                  [(equal? dtype "text_delta")
-                                   (let ([text (hash-get delta "text")])
-                                     (when text
-                                       (put-string text-acc text)
-                                       (token-cb text)))]
-                                  [(equal? dtype "input_json_delta")
-                                   (let ([idx (current-idx)]
-                                         [partial (hash-get
-                                                    delta
-                                                    "partial_json")])
-                                     (when (and idx partial)
-                                       (let ([acc (hash-ref
-                                                    tu-table
-                                                    idx
-                                                    #f)])
-                                         (when acc
-                                           (hash-put!
-                                             acc
-                                             "args"
-                                             (string-append
-                                               (or (hash-get acc "args")
-                                                   "")
-                                               partial))))))]))))
-                          ((equal? event-type "content_block_start")
-                            (let ([block (hash-get json "content_block")])
-                              (when (and block
-                                         (equal?
-                                           (hash-get block "type")
-                                           "tool_use"))
-                                (let ([idx (hash-get json "index")])
-                                  (current-idx idx)
-                                  (let ([acc (make-hash-table)])
-                                    (hash-put!
-                                      acc
-                                      "id"
-                                      (hash-get block "id"))
-                                    (hash-put!
-                                      acc
-                                      "name"
-                                      (hash-get block "name"))
-                                    (hash-put! tu-table idx acc))))))
-                          ((equal? event-type "content_block_stop")
-                            (current-idx #f))
-                          (#t (void))]))))))))
+         (let ([http-status (http-post-stream
+                              url
+                              headers
+                              (json-object->string body)
+                              (lambda (event-str)
+                                (when event-str
+                                  (let* ([lines (string-split
+                                                  event-str
+                                                  #\newline)]
+                                         [event-type #f]
+                                         [data-str #f])
+                                    (for-each
+                                      (lambda (line)
+                                        (cond
+                                          [(string-prefix? "event: " line)
+                                           (set! event-type
+                                             (substring
+                                               line
+                                               7
+                                               (string-length line)))]
+                                          [(string-prefix? "data: " line)
+                                           (set! data-str
+                                             (substring
+                                               line
+                                               6
+                                               (string-length line)))]))
+                                      lines)
+                                    (when (and event-type data-str)
+                                      (let ([json (guard (e [list #t #f])
+                                                    (string->json-object
+                                                      data-str))])
+                                        (when json
+                                          (cond
+                                            [(equal?
+                                               event-type
+                                               "content_block_delta")
+                                             (let ([delta (hash-get
+                                                            json
+                                                            "delta")])
+                                               (when delta
+                                                 (let ([dtype (hash-get
+                                                                delta
+                                                                "type")])
+                                                   (cond
+                                                     [(equal?
+                                                        dtype
+                                                        "text_delta")
+                                                      (let ([text (hash-get
+                                                                    delta
+                                                                    "text")])
+                                                        (when text
+                                                          (put-string
+                                                            text-acc
+                                                            text)
+                                                          (token-cb
+                                                            text)))]
+                                                     [(equal?
+                                                        dtype
+                                                        "input_json_delta")
+                                                      (let ([idx (current-idx)]
+                                                            [partial (hash-get
+                                                                       delta
+                                                                       "partial_json")])
+                                                        (when (and idx
+                                                                   partial)
+                                                          (let ([acc (hash-ref
+                                                                       tu-table
+                                                                       idx
+                                                                       #f)])
+                                                            (when acc
+                                                              (hash-put!
+                                                                acc
+                                                                "args"
+                                                                (string-append
+                                                                  (or (hash-get
+                                                                        acc
+                                                                        "args")
+                                                                      "")
+                                                                  partial))))))]))))
+                                             ((equal?
+                                                event-type
+                                                "content_block_start")
+                                               (let ([block (hash-get
+                                                              json
+                                                              "content_block")])
+                                                 (when (and block
+                                                            (equal?
+                                                              (hash-get
+                                                                block
+                                                                "type")
+                                                              "tool_use"))
+                                                   (let ([idx (hash-get
+                                                                json
+                                                                "index")])
+                                                     (current-idx idx)
+                                                     (let ([acc (make-hash-table)])
+                                                       (hash-put!
+                                                         acc
+                                                         "id"
+                                                         (hash-get
+                                                           block
+                                                           "id"))
+                                                       (hash-put!
+                                                         acc
+                                                         "name"
+                                                         (hash-get
+                                                           block
+                                                           "name"))
+                                                       (hash-put!
+                                                         tu-table
+                                                         idx
+                                                         acc))))))
+                                             ((equal?
+                                                event-type
+                                                "content_block_stop")
+                                               (current-idx #f))
+                                             (#t (void))]))))))))])
+           (unless (= http-status 200)
+             (log-error
+               logger
+               "stream-http-error"
+               `((status . ,http-status) (url . ,url)))))
          (let* ([content (get-output-string text-acc)]
                 [indices (sort < (hash-keys tu-table))]
                 [tool-calls (map (lambda (idx)
@@ -548,14 +659,30 @@
                                  indices)])
            (values content tool-calls))))
   (def (provider-stream-chat provider messages tools token-cb)
+       (unless (provider-api-key provider)
+         (error 'provider-stream-chat
+           (format
+             "No API key configured for provider '~a'. Set the appropriate env var or add it to jcode.json."
+             (provider-name provider))))
+       (log-info
+         logger
+         "stream-chat"
+         `((provider . ,(provider-name provider))
+            (model . ,(provider-model provider))
+            (messages . ,(length messages))))
        (case (string->symbol (provider-name provider))
          [(openai openrouter deepseek ollama)
           (openai-stream-chat provider messages tools token-cb)]
          [(anthropic)
           (anthropic-stream-chat provider messages tools token-cb)]
-         [else
+         [(google)
           (let* ([response (provider-chat provider messages tools)]
                  [content (or (message-content response) "")]
                  [tcs (or (message-tool-calls response) '())])
             (when (> (string-length content) 0) (token-cb content))
-            (values content tcs))])))
+            (values content tcs))]
+         [else
+          (error 'provider-stream-chat
+            (format
+              "Unknown provider '~a'"
+              (provider-name provider)))])))
diff --git a/lib/jcode/tool/lsp.sls b/lib/jcode/tool/lsp.sls
index 08eb38c..b03958c 100644
--- a/lib/jcode/tool/lsp.sls
+++ b/lib/jcode/tool/lsp.sls
@@ -256,17 +256,17 @@
                                   root)])
                       (lsp-initialize conn)
                       (let ([schema (make-lsp-schema)])
-                        (register-tool!
+                        (register-internal-tool!
                           "lsp_definition"
                           "Go to definition of symbol at file:line:character"
                           schema
                           handle-lsp-definition)
-                        (register-tool!
+                        (register-internal-tool!
                           "lsp_hover"
                           "Get hover information (type, docs) for symbol at file:line:character"
                           schema
                           handle-lsp-hover)
-                        (register-tool!
+                        (register-internal-tool!
                           "lsp_references"
                           "Find all references to symbol at file:line:character"
                           schema
diff --git a/lib/jcode/tool/registry.sls b/lib/jcode/tool/registry.sls
index d078b1b..f7794f6 100644
--- a/lib/jcode/tool/registry.sls
+++ b/lib/jcode/tool/registry.sls
@@ -3,8 +3,8 @@
 ;;; Source: src/jcode/tool/registry.ss
 
 (library (jcode tool registry)
-  (export register-tool! tool-execute get-tool-schemas
-    list-tools tool->openai-schema)
+  (export register-tool! register-internal-tool! tool-execute
+    get-tool-schemas list-tools tool->openai-schema)
   (import
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
@@ -26,6 +26,15 @@
        (let ([ht (make-hash-table)])
          (for-each (lambda (p) (hash-put! ht (car p) (cdr p))) pairs)
          ht))
+  (def (register-internal-tool!
+         name
+         description
+         schema
+         handler)
+       "Register a tool that won't be sent to the LLM (MCP, LSP, etc)."
+       (register-tool! name description schema handler)
+       (let ([t (hash-get *tools* name)])
+         (when t (hash-put! t "internal" #t))))
   (def (tool-execute name args)
        (log-info logger "execute" `((name . ,name)))
        (let ([tool (hash-get *tools* name)])
@@ -41,7 +50,7 @@
   (def (get-tool-schemas)
        (map tool->openai-schema
             (filter
-              (lambda (t) t)
+              (lambda (t) (and t (not (hash-get t "internal"))))
               (map (lambda (name) (hash-get *tools* name))
                    (hash-keys *tools*)))))
   (def (list-tools) (hash-keys *tools*))
diff --git a/lib/jcode/ui/tui-status.sls b/lib/jcode/ui/tui-status.sls
index 3fda9e6..d4f6917 100644
--- a/lib/jcode/ui/tui-status.sls
+++ b/lib/jcode/ui/tui-status.sls
@@ -11,7 +11,7 @@
     (std misc string) (jcode ui tui-ffi) (jcode ui tui-theme)
     (jerboa core) (jerboa runtime))
   (def (render-status-bar! x y width provider model tokens-in
-         tokens-out cost cwd mcp-ok? lsp-ok?)
+         tokens-out cost cwd)
        "Render the status bar at row y across width columns."
        (let ([bg (face-bg-attr 'status-bar)]
              [fg (face-fg-attr 'status-bar)])
@@ -50,11 +50,8 @@
                    (lloop
                      (cdr parts)
                      (+ next-col (if (pair? (cdr parts)) 3 0))))))))
-         (let* ([mcp-ind (if mcp-ok? "● MCP" "○ MCP")]
-                [lsp-ind (if lsp-ok? "● LSP" "○ LSP")]
-                [cwd-short (shorten-path cwd 20)]
-                [right-str (string-append cwd-short " │ " mcp-ind " │ "
-                             lsp-ind " ")]
+         (let* ([cwd-short (shorten-path cwd 30)]
+                [right-str (string-append " " cwd-short " ")]
                 [right-col (max 0
                                 (- (+ x width)
                                    (string-length right-str)))])
diff --git a/lib/jcode/ui/tui.sls b/lib/jcode/ui/tui.sls
index abd70f6..8c5ff65 100644
--- a/lib/jcode/ui/tui.sls
+++ b/lib/jcode/ui/tui.sls
@@ -471,41 +471,55 @@
        (app-state-stream-buf-set! state "")
        (add-message! state (msg-block-assistant ""))
        (app-state-dirty?-set! state #t)
-       (tui-log
-         "run-agent: starting, session=~a"
-         (app-state-session-id state))
-       (try (parameterize ([current-stream-cb
-                            (lambda (token)
-                              (tui-stream-token! state token))]
-                           [current-tool-cb
-                            (lambda (event name args)
-                              (tui-tool-event! state event name args))])
-              (agent-run (app-state-session-id state) text)
-              (tui-log
-                "run-agent: agent-run returned, buf=~s"
-                (app-state-stream-buf state))
-              (let ([buf (app-state-stream-buf state)])
-                (unless (string-empty? buf)
-                  (update-last-assistant! state buf))))
-            (catch
-              (e)
-              (tui-log "run-agent: ERROR ~a" (err->string e))
-              (add-message! state (msg-block-error (err->string e)))))
-       (app-state-agent-busy?-set! state #f)
-       (app-state-scroll-offset-set! state 0)
-       (app-state-dirty?-set! state #t) (draw-all! state)
-       (tb-present!))
+       (let ([p-name (or (current-provider-override)
+                         (config-provider))]
+             [m-name (or (current-model-override) (config-model))]
+             [p-override (current-provider-override)]
+             [m-override (current-model-override)]
+             [s-id (app-state-session-id state)])
+         (tui-log
+           "run-agent: spawning thread, session=~a provider=~a model=~a key?=~a"
+           s-id p-name m-name
+           (and (config-get-provider-key p-name) #t))
+         (spawn
+           (lambda ()
+             (try (parameterize ([current-provider-override p-override]
+                                 [current-model-override m-override]
+                                 [current-stream-cb
+                                  (lambda (token)
+                                    (tui-stream-token! state token))]
+                                 [current-tool-cb
+                                  (lambda (event name args)
+                                    (tui-tool-event!
+                                      state
+                                      event
+                                      name
+                                      args))])
+                    (agent-run s-id text)
+                    (tui-log
+                      "run-agent: agent-run returned, buf-len=~a"
+                      (string-length (app-state-stream-buf state)))
+                    (let ([buf (app-state-stream-buf state)])
+                      (unless (string-empty? buf)
+                        (update-last-assistant! state buf))))
+                  (catch
+                    (e)
+                    (tui-log "run-agent: ERROR ~a" (err->string e))
+                    (add-message!
+                      state
+                      (msg-block-error (err->string e)))))
+             (app-state-agent-busy?-set! state #f)
+             (app-state-scroll-offset-set! state 0)
+             (app-state-dirty?-set! state #t)))))
   (def (tui-stream-token! state token)
-       "Handle a streaming token from the LLM."
+       "Handle a streaming token from the LLM — called from agent thread."
        (let ([buf (string-append
                     (app-state-stream-buf state)
                     token)])
          (app-state-stream-buf-set! state buf)
          (update-last-assistant! state buf)
          (app-state-scroll-offset-set! state 0)
-         (app-state-dirty?-set! state #t)
-         (draw-all! state)
-         (tb-present!)))
+         (app-state-dirty?-set! state #t)))
   (def (tui-tool-event! state event name args)
        (case event
          [(start)
@@ -514,9 +528,7 @@
           (let ([tc (app-state-tool-counts state)])
             (hash-put! tc name (+ 1 (or (hash-get tc name) 0))))
           (update-sidebar-tools! state)
-          (app-state-dirty?-set! state #t)
-          (draw-all! state)
-          (tb-present!)]
+          (app-state-dirty?-set! state #t)]
          [(end)
           (let ([msgs (app-state-messages state)])
             (let loop ([msgs (reverse msgs)])
@@ -596,9 +608,7 @@
            (or (current-provider-override) (config-provider))
            (or (current-model-override) (config-model))
            (app-state-tokens-in state) (app-state-tokens-out state)
-           (app-state-cost state) (current-directory)
-           (not (null? (sidebar-state-mcp (app-state-sidebar state))))
-           (and (sidebar-state-lsp (app-state-sidebar state)) #t))
+           (app-state-cost state) (current-directory))
          (when (app-state-sidebar-visible? state)
            (render-sidebar! (app-state-sidebar state) (sidebar-x state) 0
              (app-state-sidebar-width state)
diff --git a/src/jcode/core/config.ss b/src/jcode/core/config.ss
index 201df0a..ba5a55e 100644
--- a/src/jcode/core/config.ss
+++ b/src/jcode/core/config.ss
@@ -64,8 +64,11 @@
     config))
 
 (def (merge-opencode-keys! providers)
-  ;; Read opencode auth.json for provider keys
-  (let ((auth-file (path-join (getenv "HOME") ".local" "share" "opencode" "auth.json")))
+  ;; Read auth.json for provider keys (check ~/.jcode first, then opencode)
+  (let ((auth-file (let ((jcode-auth (path-join (jcode-home) "auth.json")))
+                     (if (file-exists? jcode-auth)
+                       jcode-auth
+                       (path-join (getenv "HOME") ".local" "share" "opencode" "auth.json")))))
     (when (file-exists? auth-file)
       (try
         (let ((auth (call-with-input-file auth-file read-json)))
diff --git a/src/jcode/core/models.ss b/src/jcode/core/models.ss
index 788aaad..8820485 100644
--- a/src/jcode/core/models.ss
+++ b/src/jcode/core/models.ss
@@ -67,16 +67,42 @@
     ("gemini-2.0-flash" . "Gemini 2.0 Flash")))
 
 (def openrouter-models
-  '(("anthropic/claude-sonnet-4"     . "Claude Sonnet 4")
-    ("anthropic/claude-opus-4"       . "Claude Opus 4")
-    ("openai/gpt-4o"                 . "GPT-4o")
-    ("google/gemini-2.5-pro"         . "Gemini 2.5 Pro")
-    ("google/gemini-2.5-flash"       . "Gemini 2.5 Flash")
-    ("deepseek/deepseek-chat"        . "DeepSeek V3")
-    ("deepseek/deepseek-r1"          . "DeepSeek R1")
-    ("meta-llama/llama-4-maverick"   . "Llama 4 Maverick")
-    ("meta-llama/llama-4-scout"      . "Llama 4 Scout")
-    ("qwen/qwen3-235b-a22b"         . "Qwen3 235B")))
+  '(;; Anthropic
+    ("anthropic/claude-sonnet-4"          . "Claude Sonnet 4")
+    ("anthropic/claude-opus-4"            . "Claude Opus 4")
+    ("anthropic/claude-haiku-3.5"         . "Claude 3.5 Haiku")
+    ;; OpenAI
+    ("openai/gpt-4o"                      . "GPT-4o")
+    ("openai/gpt-4o-mini"                 . "GPT-4o Mini")
+    ("openai/o3"                          . "o3")
+    ("openai/o3-mini"                     . "o3 Mini")
+    ("openai/o4-mini"                     . "o4 Mini")
+    ;; Google
+    ("google/gemini-2.5-pro"              . "Gemini 2.5 Pro")
+    ("google/gemini-2.5-flash"            . "Gemini 2.5 Flash")
+    ("google/gemini-2.0-flash"            . "Gemini 2.0 Flash")
+    ;; Meta
+    ("meta-llama/llama-4-maverick"        . "Llama 4 Maverick")
+    ("meta-llama/llama-4-scout"           . "Llama 4 Scout")
+    ("meta-llama/llama-3.3-70b-instruct" . "Llama 3.3 70B")
+    ;; DeepSeek
+    ("deepseek/deepseek-chat-v3-0324"     . "DeepSeek V3 0324")
+    ("deepseek/deepseek-r1"               . "DeepSeek R1")
+    ;; Qwen
+    ("qwen/qwen3-235b-a22b"              . "Qwen3 235B")
+    ("qwen/qwen3-30b-a3b"               . "Qwen3 30B")
+    ;; Mistral
+    ("mistralai/mistral-large"            . "Mistral Large")
+    ("mistralai/mistral-medium"           . "Mistral Medium")
+    ("mistralai/codestral"                . "Codestral")
+    ;; Cohere
+    ("cohere/command-r-plus"              . "Command R+")
+    ;; xAI
+    ("x-ai/grok-3"                        . "Grok 3")
+    ("x-ai/grok-3-mini"                   . "Grok 3 Mini")
+    ;; Other
+    ("microsoft/phi-4"                    . "Phi-4")
+    ("nousresearch/hermes-3-llama-3.1-405b" . "Hermes 3 405B")))
 
 (def ollama-models
   '(("llama3.2"        . "Llama 3.2")
diff --git a/src/jcode/core/plugin.ss b/src/jcode/core/plugin.ss
index 40e00e8..c28dd32 100644
--- a/src/jcode/core/plugin.ss
+++ b/src/jcode/core/plugin.ss
@@ -37,9 +37,20 @@
                 (if (list? entries) entries '())))))
         dirs))))
 
+(def (ensure-library-dirs!)
+  "Ensure library-directories includes Jerboa lib dir for plugin imports."
+  (let ((jerboa-lib (path-join (or (getenv "JERBOA_HOME")
+                                    (path-join (getenv "HOME") "mine/jerboa"))
+                                "lib")))
+    (when (file-directory? jerboa-lib)
+      (let ((entry (cons jerboa-lib jerboa-lib)))
+        (unless (member entry (library-directories))
+          (library-directories (cons entry (library-directories))))))))
+
 (def (load-plugin path)
   "Load a single plugin file."
   (log-info logger "loading" `((path . ,path)))
+  (ensure-library-dirs!)
   (try
     (load path)
     (set! *loaded-plugins* (cons path *loaded-plugins*))
diff --git a/src/jcode/mcp/client.ss b/src/jcode/mcp/client.ss
index f5ede4e..d252d27 100644
--- a/src/jcode/mcp/client.ss
+++ b/src/jcode/mcp/client.ss
@@ -182,7 +182,7 @@
               (desc (or (hash-get tool "description") "MCP tool"))
               (schema (or (hash-get tool "inputSchema") (make-hash-table))))
           (let ((jcode-name (string-append prefix name)))
-            (register-tool! jcode-name desc schema
+            (register-internal-tool! jcode-name desc schema
               (lambda (args)
                 (mcp-call-tool conn name args))))))
       tools)
@@ -199,6 +199,7 @@
 
 (def (init-mcp-tools)
   "Initialize all configured MCP servers."
+  (mcp-stop-all!)  ;; clear any prior connections
   (let ((configs (load-mcp-config)))
     (for-each
       (lambda (pair)
diff --git a/src/jcode/provider/provider.ss b/src/jcode/provider/provider.ss
index f2aed72..2477c58 100644
--- a/src/jcode/provider/provider.ss
+++ b/src/jcode/provider/provider.ss
@@ -343,9 +343,16 @@
          (text-acc (open-output-string))
          ;; tool-call accumulators: index -> alist with id/name/args-so-far
          (tc-table (make-hash-table)))
-    (http-post-stream url headers (json-object->string body)
+    (let* ((body-json (json-object->string body))
+           (dummy (log-info logger "stream-request"
+                    `((url . ,url) (body-len . ,(string-length body-json)))))
+           (http-status
+           (http-post-stream url headers body-json
       (lambda (event-str)
         (when event-str
+          (log-info logger "sse-event" `((data . ,(if (> (string-length event-str) 120)
+                                                     (substring event-str 0 120)
+                                                     event-str))))
           ;; Strip "data: " prefix
           (let* ((data (if (string-prefix? "data: " event-str)
                          (substring event-str 6 (string-length event-str))
@@ -387,7 +394,10 @@
                                          (string-append
                                            (or (hash-get acc "args") "")
                                            args)))))))
-                             tcs)))))))))))))
+                             tcs)))))))))))))))
+      (unless (= http-status 200)
+        (log-error logger "stream-http-error"
+          `((status . ,http-status) (url . ,url)))))
     ;; Build result
     (let* ((content (get-output-string text-acc))
            (indices (sort < (hash-keys tc-table)))
@@ -399,6 +409,9 @@
                         (or (hash-get acc "name") "unknown")
                         (or (hash-get acc "args") "{}"))))
                   indices)))
+      (log-info logger "stream-result"
+        `((content-len . ,(string-length content))
+          (tool-calls  . ,(length tool-calls))))
       (values content tool-calls))))
 
 ;;; Anthropic Streaming ;;;
@@ -422,7 +435,8 @@
          ;; tool use accumulators: id -> alist
          (tu-table    (make-hash-table))
          (current-idx (make-parameter #f)))
-    (http-post-stream url headers (json-object->string body)
+    (let ((http-status
+           (http-post-stream url headers (json-object->string body)
       (lambda (event-str)
         (when event-str
           ;; Anthropic SSE uses multiple lines per event: "event: ...\ndata: ..."
@@ -476,7 +490,10 @@
                     ;; Block ended
                     ((equal? event-type "content_block_stop")
                      (current-idx #f))
-                    (#t (void)))))))))))
+                    (#t (void)))))))))))))
+      (unless (= http-status 200)
+        (log-error logger "stream-http-error"
+          `((status . ,http-status) (url . ,url)))))
     ;; Build result
     (let* ((content    (get-output-string text-acc))
            (indices    (sort < (hash-keys tu-table)))
@@ -493,13 +510,24 @@
 ;; provider-stream-chat: stream text tokens to token-cb, accumulate tool calls.
 ;; Returns (values content-str tool-call-list).
 (def (provider-stream-chat provider messages tools token-cb)
+  (unless (provider-api-key provider)
+    (error 'provider-stream-chat
+      (format "No API key configured for provider '~a'. Set the appropriate env var or add it to jcode.json."
+        (provider-name provider))))
+  (log-info logger "stream-chat"
+    `((provider . ,(provider-name provider))
+      (model    . ,(provider-model provider))
+      (messages . ,(length messages))))
   (case (string->symbol (provider-name provider))
     ((openai openrouter deepseek ollama) (openai-stream-chat  provider messages tools token-cb))
     ((anthropic)                         (anthropic-stream-chat provider messages tools token-cb))
-    ;; Fallback: non-streaming for other providers
-    (else
+    ((google)
+     ;; Google: non-streaming fallback
      (let* ((response (provider-chat provider messages tools))
             (content  (or (message-content response) ""))
             (tcs      (or (message-tool-calls response) '())))
        (when (> (string-length content) 0) (token-cb content))
-       (values content tcs)))))
+       (values content tcs)))
+    (else
+     (error 'provider-stream-chat
+       (format "Unknown provider '~a'" (provider-name provider))))))
diff --git a/src/jcode/tool/lsp.ss b/src/jcode/tool/lsp.ss
index 0387bbd..8ed04ba 100644
--- a/src/jcode/tool/lsp.ss
+++ b/src/jcode/tool/lsp.ss
@@ -253,13 +253,13 @@
             (let ((conn (lsp-start command (if (list? args) args '()) root)))
               (lsp-initialize conn)
               (let ((schema (make-lsp-schema)))
-                (register-tool! "lsp_definition"
+                (register-internal-tool! "lsp_definition"
                   "Go to definition of symbol at file:line:character"
                   schema handle-lsp-definition)
-                (register-tool! "lsp_hover"
+                (register-internal-tool! "lsp_hover"
                   "Get hover information (type, docs) for symbol at file:line:character"
                   schema handle-lsp-hover)
-                (register-tool! "lsp_references"
+                (register-internal-tool! "lsp_references"
                   "Find all references to symbol at file:line:character"
                   schema handle-lsp-references))
               (log-info logger "ready" `((tools . 3))))
diff --git a/src/jcode/tool/registry.ss b/src/jcode/tool/registry.ss
index b7dbe5c..cad3e1a 100644
--- a/src/jcode/tool/registry.ss
+++ b/src/jcode/tool/registry.ss
@@ -1,6 +1,7 @@
 ;;; jcode tool registry
 
 (export register-tool!
+        register-internal-tool!
         tool-execute
         get-tool-schemas
         list-tools
@@ -26,6 +27,12 @@
     (for-each (lambda (p) (hash-put! ht (car p) (cdr p))) pairs)
     ht))
 
+(def (register-internal-tool! name description schema handler)
+  "Register a tool that won't be sent to the LLM (MCP, LSP, etc)."
+  (register-tool! name description schema handler)
+  (let ((t (hash-get *tools* name)))
+    (when t (hash-put! t "internal" #t))))
+
 (def (tool-execute name args)
   (log-info logger "execute" `((name . ,name)))
   (let ((tool (hash-get *tools* name)))
@@ -38,7 +45,8 @@
 
 (def (get-tool-schemas)
   (map tool->openai-schema
-       (filter (lambda (t) t)
+       (filter (lambda (t)
+                 (and t (not (hash-get t "internal"))))
                (map (lambda (name) (hash-get *tools* name))
                     (hash-keys *tools*)))))
 
diff --git a/src/jcode/ui/tui-status.ss b/src/jcode/ui/tui-status.ss
index e4baab5..7e0c264 100644
--- a/src/jcode/ui/tui-status.ss
+++ b/src/jcode/ui/tui-status.ss
@@ -7,7 +7,7 @@