Add debug REPL, fix http-get-json hang on plain HTTP, fix ESC in TUI
ober
17fac527850db5693b05af071930a4ce29ec36f4
--- a/build-binary.ss +++ b/build-binary.ss @@ -108,6 +108,7 @@ "lib/jcode/core/message" "lib/jcode/core/agent" "lib/jcode/core/plugin" + "lib/jcode/core/debug-repl" "lib/jcode/provider/provider" "lib/jcode/tool/registry" "lib/jcode/tool/file" new file mode 100644 --- /dev/null +++ b/lib/jcode/core/debug-repl.sls @@ -0,0 +1,215 @@ +#!chezscheme +;;; Generated by jerbuild — DO NOT EDIT +;;; Source: src/jcode/core/debug-repl.ss + +(library (jcode core debug-repl) + (export start-jcode-repl! stop-jcode-repl! jcode-repl-port) + (import + (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;- + getenv path-extension path-absolute? thread? make-mutex + mutex? mutex-name) + (jcode core log) + (jerboa core) + (jerboa runtime)) + (def logger (make-logger "debug-repl")) + (def _libc-loaded + (guard (e [#t #f]) (load-shared-object #f))) + (def c-socket + (foreign-procedure "socket" (int int int) int)) + (def c-bind (foreign-procedure "bind" (int void* int) int)) + (def c-listen (foreign-procedure "listen" (int int) int)) + (def c-accept + (foreign-procedure "accept" (int void* void*) int)) + (def c-close (foreign-procedure "close" (int) int)) + (def c-setsockopt + (foreign-procedure "setsockopt" + (int int int void* int) + int)) + (def c-htons + (foreign-procedure "htons" (unsigned-short) unsigned-short)) + (def c-getsockname + (foreign-procedure "getsockname" (int void* void*) int)) + (def c-fcntl (foreign-procedure "fcntl" (int int int) int)) + (def c-errno-location + (let ([mt (symbol->string (machine-type))]) + (if (and (>= (string-length mt) 3) + (string=? + (substring + mt + (- (string-length mt) 3) + (string-length mt)) + "osx")) + (foreign-procedure "__error" () void*) + (foreign-procedure "__errno_location" () void*)))) + (def (get-errno) (foreign-ref 'int (c-errno-location) 0)) + (def AF_INET 2) + (def SOCK_STREAM 1) + (def SOL_SOCKET 65535) + (def SO_REUSEADDR 4) + (def SOCKADDR_IN_SIZE 16) + (def F_GETFL 3) + (def F_SETFL 4) + (def O_NONBLOCK 4) + (def EINTR 4) + (def EAGAIN 35) + (def *retry-delay* (make-time 'time-duration 10000000 0)) + (def (set-nonblocking! fd) + (let ([flags (c-fcntl fd F_GETFL 0)]) + (c-fcntl fd F_SETFL (bitwise-ior flags O_NONBLOCK)))) + (def (make-sockaddr-in port) + (let ([buf (foreign-alloc SOCKADDR_IN_SIZE)]) + (let lp ([i 0]) + (when (< i SOCKADDR_IN_SIZE) + (foreign-set! 'unsigned-8 buf i 0) + (lp (+ i 1)))) + (foreign-set! 'unsigned-8 buf 0 SOCKADDR_IN_SIZE) + (foreign-set! 'unsigned-8 buf 1 AF_INET) + (foreign-set! 'unsigned-short buf 2 (c-htons port)) + (foreign-set! 'unsigned-8 buf 4 127) + (foreign-set! 'unsigned-8 buf 5 0) + (foreign-set! 'unsigned-8 buf 6 0) + (foreign-set! 'unsigned-8 buf 7 1) + buf)) + (def (sockaddr-in-port buf) + (let ([hi (foreign-ref 'unsigned-8 buf 2)] + [lo (foreign-ref 'unsigned-8 buf 3)]) + (+ (* hi 256) lo))) + (def *server-fd* #f) + (def *server-port* #f) + (def *server-running* #f) + (def *repl-port-file* + (string-append + (or (getenv "HOME") ".") + "/.jcode-repl-port")) + (def (write-port-file! port-num) + (when (file-exists? *repl-port-file*) + (delete-file *repl-port-file*)) + (call-with-output-file + *repl-port-file* + (lambda (p) (display port-num p) (newline p)))) + (def (delete-port-file!) + (when (file-exists? *repl-port-file*) + (guard (e [#t (void)]) (delete-file *repl-port-file*)))) + (def (client-repl fd) + (let ([ip (open-fd-input-port + fd + (buffer-mode block) + (make-transcoder + (utf-8-codec) + (eol-style none) + (error-handling-mode replace)))] + [op (open-fd-output-port + fd + (buffer-mode line) + (make-transcoder + (utf-8-codec) + (eol-style lf) + (error-handling-mode replace)))]) + (dynamic-wind + void + (lambda () + (let loop () + (guard (e [#t (void)]) + (display "jcode> " op) + (flush-output-port op) + (let ([expr (guard (e + [#t + (display + (format + #f + "read error: ~a\n" + (if (message-condition? e) + (condition-message e) + e)) + op) + (flush-output-port op) + 'continue]) + (read ip))]) + (cond + [(eof-object? expr) (void)] + [(eq? expr 'continue) (loop)] + [else + (guard (e + [#t + (display + (format + #f + "error: ~a\n" + (if (message-condition? e) + (condition-message e) + e)) + op) + (flush-output-port op)]) + (call-with-values + (lambda () (eval expr (interaction-environment))) + (lambda results + (for-each + (lambda (v) + (unless (eq? v (void)) + (write v op) + (newline op))) + results) + (flush-output-port op)))) + (loop)]))))) + (lambda () + (guard (e [#t (void)]) (close-port ip)) + (guard (e [#t (void)]) (close-port op)))))) + (def (accept-loop server-fd) + (let loop () + (when *server-running* + (let ([client-fd (c-accept server-fd 0 0)]) + (cond + [(>= client-fd 0) + (fork-thread (lambda () (client-repl client-fd))) + (loop)] + [(let ([e (get-errno)]) (or (= e EINTR) (= e EAGAIN))) + (sleep *retry-delay*) + (loop)] + [else + (when *server-running* (sleep *retry-delay*) (loop))]))))) + (def (start-jcode-repl! . args) + "Start the debug REPL. Pass a port number or 0 for auto-assign." + (stop-jcode-repl!) + (let ([port (if (pair? args) (car args) 0)]) + (let ([fd (c-socket AF_INET SOCK_STREAM 0)]) + (when (< fd 0) (error 'start-jcode-repl! "socket() failed")) + (let ([one (foreign-alloc 4)]) + (foreign-set! 'int one 0 1) + (c-setsockopt fd SOL_SOCKET SO_REUSEADDR one 4) + (foreign-free one)) + (let ([addr (make-sockaddr-in port)]) + (let ([rc (c-bind fd addr SOCKADDR_IN_SIZE)]) + (foreign-free addr) + (when (< rc 0) + (c-close fd) + (error 'start-jcode-repl! "bind() failed" port)))) + (when (< (c-listen fd 4) 0) + (c-close fd) + (error 'start-jcode-repl! "listen() failed")) + (set-nonblocking! fd) + (let ([buf (foreign-alloc SOCKADDR_IN_SIZE)] + [len (foreign-alloc 4)]) + (foreign-set! 'int len 0 SOCKADDR_IN_SIZE) + (c-getsockname fd buf len) + (let ([actual-port (sockaddr-in-port buf)]) + (foreign-free buf) + (foreign-free len) + (set! *server-fd* fd) + (set! *server-port* actual-port) + (set! *server-running* #t) + (write-port-file! actual-port) + (fork-thread (lambda () (accept-loop fd))) + (log-info logger "started" `((port . ,actual-port))) + actual-port))))) + (def (stop-jcode-repl!) + "Stop the debug REPL if running." + (when *server-running* + (set! *server-running* #f) + (when *server-fd* + (guard (e [#t (void)]) (c-close *server-fd*)) + (set! *server-fd* #f) + (set! *server-port* #f)) + (delete-port-file!))) + (def (jcode-repl-port) + "Return the port the debug REPL is listening on, or #f." + *server-port*)) --- a/lib/jcode/provider/provider.sls +++ b/lib/jcode/provider/provider.sls @@ -1187,17 +1187,12 @@ (let* ([status-line (port-read-line in)] [status (parse-http-status status-line)] [resp-headers (port-read-headers in)] - [te (header-value + [cl (header-value resp-headers - "transfer-encoding")] - [raw (port-read-all-bytes in)] - [body-bv (if (and te - (string-contains - te - "chunked")) - (decode-chunked-bytes raw) - raw)] - [body (utf8->string body-bv)]) + "content-length")] + [body (if cl + (get-string-n in (string->number cl)) + (port-read-all in))]) (values status body))) (lambda () (close-port in) (close-port out)))))))) (def (openai-list-models provider) --- a/lib/jcode/ui/cli.sls +++ b/lib/jcode/ui/cli.sls @@ -11,11 +11,11 @@ (std misc string) (std misc thread) (std text json) (jcode core config) (jcode core log) (jcode core session) (jcode core message) (jcode core agent) - (jcode tool registry) (jcode tool file) (jcode tool bash) - (jcode tool web) (jcode tool batch) (jcode tool git) - (jcode mcp client) (jcode tool lsp) (jcode core plugin) - (jcode ui tui) (jcode ui serve) (jerboa core) - (jerboa runtime)) + (jcode core debug-repl) (jcode tool registry) + (jcode tool file) (jcode tool bash) (jcode tool web) + (jcode tool batch) (jcode tool git) (jcode mcp client) + (jcode tool lsp) (jcode core plugin) (jcode ui tui) + (jcode ui serve) (jerboa core) (jerboa runtime)) (def logger (make-logger "cli")) (def *version* "0.1.0") (def *cli-mcp-disabled* #f) @@ -36,6 +36,13 @@ (load-config) (session-init-db) (init-tools) + (let ([repl-opt (assoc '\x2D;-repl-port opts)]) + (when repl-opt + (let ([port (start-jcode-repl! (cdr repl-opt))]) + (printf + "Debug REPL on port ~a (nc 127.0.0.1 ~a)~n" + port + port)))) (let ([p-opt (assoc '\x2D;-provider opts)] [m-opt (assoc '\x2D;-model opts)]) (when p-opt (current-provider-override (cdr p-opt))) @@ -73,6 +80,14 @@ (loop (cdr args) (cons '(\x2D;-no-mcp . #t) opts))] [(equal? (car args) "--verbose") (loop (cdr args) (cons '(\x2D;-verbose . #t) opts))] + [(and (equal? (car args) "--repl-port") (pair? (cdr args))) + (loop + (cddr args) + (cons + (cons '\x2D;-repl-port (string->number (cadr args))) + opts))] + [(equal? (car args) "--repl") + (loop (cdr args) (cons '(\x2D;-repl-port . 0) opts))] [(and (equal? (car args) "--model") (pair? (cdr args))) (loop (cddr args) @@ -101,7 +116,7 @@ (thread-join! t-plugins))) (def (display-help) (display - "jcode - Portable AI coding agent\n\nUSAGE:\n jcode [OPTIONS] [PROMPT]\n jcode [COMMAND]\n\nOPTIONS:\n -h, --help Show this help message\n -v, --version Show version\n -d, --debug Enable debug logging\n -m, --model Model to use (default: claude-sonnet-4-20250514)\n -p, --provider Provider to use (default: anthropic)\n --tui Launch terminal UI mode\n --no-tui Force line-mode REPL (default)\n --no-mcp Skip MCP server initialization\n --verbose Log TUI events to ~/jcode.log\n\nCOMMANDS:\n session list List all sessions\n session resume Resume a previous session\n config Show or edit configuration\n serve Start JSONL server (stdio mode)\n serve --port N Start TCP server on 127.0.0.1:N\n serve --rotate-token Generate new auth token and exit\n serve --show-token Display current auth token and exit\n\nEXAMPLES:\n jcode Start interactive session\n jcode \"Read main.ss\" One-shot query\n jcode session list List sessions\n jcode serve --port 8321 Start TCP server for Android app\n")) + "jcode - Portable AI coding agent\n\nUSAGE:\n jcode [OPTIONS] [PROMPT]\n jcode [COMMAND]\n\nOPTIONS:\n -h, --help Show this help message\n -v, --version Show version\n -d, --debug Enable debug logging\n -m, --model Model to use (default: claude-sonnet-4-20250514)\n -p, --provider Provider to use (default: anthropic)\n --tui Launch terminal UI mode\n --no-tui Force line-mode REPL (default)\n --no-mcp Skip MCP server initialization\n --repl Start debug REPL on auto-assigned port\n --repl-port N Start debug REPL on specific port\n --verbose Log TUI events to ~/jcode.log\n\nCOMMANDS:\n session list List all sessions\n session resume Resume a previous session\n config Show or edit configuration\n serve Start JSONL server (stdio mode)\n serve --port N Start TCP server on 127.0.0.1:N\n serve --rotate-token Generate new auth token and exit\n serve --show-token Display current auth token and exit\n\nEXAMPLES:\n jcode Start interactive session\n jcode \"Read main.ss\" One-shot query\n jcode session list List sessions\n jcode serve --port 8321 Start TCP server for Android app\n")) (def (interactive-mode opts) (printf "jcode ~a~n" *version*) (printf --- a/lib/jcode/ui/tui.sls +++ b/lib/jcode/ui/tui.sls @@ -15,11 +15,12 @@ (jcode ui tui-input) (jcode ui tui-sidebar) (jcode ui tui-dialog) (jcode core config) (jcode core models) (jcode core agent) - (jcode provider provider) (jcode core session) - (jcode core log) (jcode tool registry) (jcode tool file) - (jcode tool bash) (jcode tool web) (jcode tool batch) - (jcode tool git) (jcode mcp client) (jcode tool lsp) - (jcode core plugin) (jerboa core) (jerboa runtime)) + (jcode core debug-repl) (jcode provider provider) + (jcode core session) (jcode core log) (jcode tool registry) + (jcode tool file) (jcode tool bash) (jcode tool web) + (jcode tool batch) (jcode tool git) (jcode mcp client) + (jcode tool lsp) (jcode core plugin) (jerboa core) + (jerboa runtime)) (def logger (make-logger "tui")) (def *version* "0.1.0") (def *tui-log-port* (make-parameter #f)) @@ -98,13 +99,14 @@ (- (app-state-width state) (app-state-sidebar-width state))) (def (tui-main args) (load-config) (session-init-db) (init-tools-for-tui) (apply-tui-overrides! args) + (let ([repl-port (start-jcode-repl!)]) (void)) (let ([verbose? (and (member "--verbose" args) #t)]) (when verbose? (open-tui-log!)) (tui-log "tui-main: starting, args=~a" args) (redirect-stderr-for-tui! verbose?)) (with-tui (tui-log "tui-main: tb-init done") - (let ([in-mode (bitwise-ior TB_INPUT_ALT TB_INPUT_MOUSE)]) + (let ([in-mode (bitwise-ior TB_INPUT_ESC TB_INPUT_MOUSE)]) (tui-log "tui-main: setting input-mode=~a output-mode=~a" in-mode @@ -138,6 +140,7 @@ "tui-main: entering event loop, main-thread=~a" (*main-thread*)) (event-loop state) + (stop-jcode-repl!) (restore-stderr!) (close-tui-log!)))) (def (init-tools-for-tui) (init-file-tools) (init-bash-tool) @@ -588,7 +591,11 @@ (let* ([cur-p (or (current-provider-override) (config-provider))] [cur-m (or (current-model-override) (config-model))] - [models (provider-models cur-p)] + [models (if (equal? cur-p "ollama") + (try (let ([prov (make-provider "ollama" #f #f)]) + (provider-list-models prov)) + (catch (e) (provider-models cur-p))) + (provider-models cur-p))] [items (map (lambda (m) (cons (format new file mode 100644 --- /dev/null +++ b/src/jcode/core/debug-repl.ss @@ -0,0 +1,246 @@ +;;; jcode debug REPL — TCP REPL for inspecting a running jcode instance +;;; +;;; Starts a TCP REPL on 127.0.0.1 that you can connect to with: +;;; rlwrap nc 127.0.0.1 $(cat ~/.jcode-repl-port) +;;; +;;; The REPL evaluates in (interaction-environment), so you can import +;;; any module and inspect live state: +;;; (import (jcode provider provider)) +;;; (provider-list-models (make-provider "ollama" #f #f)) +;;; +;;; API: +;;; (start-jcode-repl!) -> port-number (auto-assign) +;;; (start-jcode-repl! port) -> port-number +;;; (stop-jcode-repl!) -> void +;;; (jcode-repl-port) -> port-number or #f + +(export start-jcode-repl! + stop-jcode-repl! + jcode-repl-port) + +(import :jcode/core/log) + +(def logger (make-logger "debug-repl")) + +;; ---- FFI (socket primitives) ---- + +;; Ensure libc symbols are visible to foreign-procedure lookups. +;; In a static binary the symbols are already linked; (load-shared-object #f) +;; tells Chez to search the running process image. +(def _libc-loaded + (guard (e [#t #f]) + (load-shared-object #f))) + +(def c-socket (foreign-procedure "socket" (int int int) int)) +(def c-bind (foreign-procedure "bind" (int void* int) int)) +(def c-listen (foreign-procedure "listen" (int int) int)) +(def c-accept (foreign-procedure "accept" (int void* void*) int)) +(def c-close (foreign-procedure "close" (int) int)) +(def c-setsockopt (foreign-procedure "setsockopt" (int int int void* int) int)) +(def c-htons (foreign-procedure "htons" (unsigned-short) unsigned-short)) +(def c-getsockname (foreign-procedure "getsockname" (int void* void*) int)) +(def c-fcntl (foreign-procedure "fcntl" (int int int) int)) + +(def c-errno-location + (let ((mt (symbol->string (machine-type)))) + (if (and (>= (string-length mt) 3) + (string=? (substring mt (- (string-length mt) 3) (string-length mt)) "osx")) + (foreign-procedure "__error" () void*) + (foreign-procedure "__errno_location" () void*)))) + +(def (get-errno) (foreign-ref 'int (c-errno-location) 0)) + +;; ---- Constants ---- + +(def AF_INET 2) +(def SOCK_STREAM 1) +(def SOL_SOCKET #xffff) ;; macOS/FreeBSD; Linux would be 1 +(def SO_REUSEADDR 4) ;; macOS/FreeBSD; Linux would be 2 +(def SOCKADDR_IN_SIZE 16) +(def F_GETFL 3) +(def F_SETFL 4) +(def O_NONBLOCK #x4) ;; macOS; Linux would be #x800 +(def EINTR 4) +(def EAGAIN 35) ;; macOS; Linux would be 11 +(def *retry-delay* (make-time 'time-duration 10000000 0)) ;; 10ms + +;; ---- Helpers ---- + +(def (set-nonblocking! fd) + (let ((flags (c-fcntl fd F_GETFL 0))) + (c-fcntl fd F_SETFL (bitwise-ior flags O_NONBLOCK)))) + +(def (make-sockaddr-in port) + ;; Bind to 127.0.0.1 only (not 0.0.0.0) + (let ((buf (foreign-alloc SOCKADDR_IN_SIZE))) + (let lp ((i 0)) + (when (< i SOCKADDR_IN_SIZE) + (foreign-set! 'unsigned-8 buf i 0) + (lp (+ i 1)))) + ;; macOS sockaddr_in: sin_len(1) + sin_family(1) + sin_port(2) + sin_addr(4) + (foreign-set! 'unsigned-8 buf 0 SOCKADDR_IN_SIZE) ;; sin_len + (foreign-set! 'unsigned-8 buf 1 AF_INET) ;; sin_family + (foreign-set! 'unsigned-short buf 2 (c-htons port)) + ;; 127.0.0.1 = bytes 127, 0, 0, 1 at offset 4 + (foreign-set! 'unsigned-8 buf 4 127) + (foreign-set! 'unsigned-8 buf 5 0) + (foreign-set! 'unsigned-8 buf 6 0) + (foreign-set! 'unsigned-8 buf 7 1) + buf)) + +(def (sockaddr-in-port buf) + (let ((hi (foreign-ref 'unsigned-8 buf 2)) + (lo (foreign-ref 'unsigned-8 buf 3))) + (+ (* hi 256) lo))) + +;; ---- State ---- + +(def *server-fd* #f) +(def *server-port* #f) +(def *server-running* #f) + +(def *repl-port-file* + (string-append (or (getenv "HOME") ".") "/.jcode-repl-port")) + +;; ---- Port file ---- + +(def (write-port-file! port-num) + (when (file-exists? *repl-port-file*) + (delete-file *repl-port-file*)) + (call-with-output-file *repl-port-file* + (lambda (p) + (display port-num p) + (newline p)))) + +(def (delete-port-file!) + (when (file-exists? *repl-port-file*) + (guard (e [#t (void)]) + (delete-file *repl-port-file*)))) + +;; ---- Client REPL ---- + +(def (client-repl fd) + (let ((ip (open-fd-input-port fd (buffer-mode block) + (make-transcoder (utf-8-codec) (eol-style none) + (error-handling-mode replace)))) + (op (open-fd-output-port fd (buffer-mode line) + (make-transcoder (utf-8-codec) (eol-style lf) + (error-handling-mode replace))))) + (dynamic-wind + void + (lambda () + (let loop () + (guard (e [#t (void)]) ;; I/O error — stop + (display "jcode> " op) + (flush-output-port op) + (let ((expr (guard (e [#t + (display (format #f "read error: ~a\n" + (if (message-condition? e) + (condition-message e) + e)) + op) + (flush-output-port op) + 'continue]) + (read ip)))) + (cond + ((eof-object? expr) (void)) ;; client disconnected + ((eq? expr 'continue) (loop)) + (else + (guard (e [#t + (display (format #f "error: ~a\n" + (if (message-condition? e) + (condition-message e) + e)) + op) + (flush-output-port op)]) + (call-with-values + (lambda () (eval expr (interaction-environment))) + (lambda results + (for-each + (lambda (v) + (unless (eq? v (void)) + (write v op) + (newline op))) + results) + (flush-output-port op)))) + (loop))))))) + (lambda () + (guard (e [#t (void)]) (close-port ip)) + (guard (e [#t (void)]) (close-port op)))))) + +;; ---- Accept loop ---- + +(def (accept-loop server-fd) + (let loop () + (when *server-running* + (let ((client-fd (c-accept server-fd 0 0))) + (cond + ((>= client-fd 0) + (fork-thread (lambda () (client-repl client-fd))) + (loop)) + ((let ((e (get-errno))) (or (= e EINTR) (= e EAGAIN))) + (sleep *retry-delay*) + (loop)) + (else + (when *server-running* + (sleep *retry-delay*) + (loop)))))))) + +;; ---- Public API ---- + +(def (start-jcode-repl! . args) + "Start the debug REPL. Pass a port number or 0 for auto-assign." + (stop-jcode-repl!) + (let ((port (if (pair? args) (car args) 0))) + (let ((fd (c-socket AF_INET SOCK_STREAM 0))) + (when (< fd 0) + (error 'start-jcode-repl! "socket() failed")) + ;; SO_REUSEADDR + (let ((one (foreign-alloc 4))) + (foreign-set! 'int one 0 1) + (c-setsockopt fd SOL_SOCKET SO_REUSEADDR one 4) + (foreign-free one)) + ;; Bind to 127.0.0.1 + (let ((addr (make-sockaddr-in port))) + (let ((rc (c-bind fd addr SOCKADDR_IN_SIZE))) + (foreign-free addr) + (when (< rc 0) + (c-close fd) + (error 'start-jcode-repl! "bind() failed" port)))) + ;; Listen + (when (< (c-listen fd 4) 0) + (c-close fd) + (error 'start-jcode-repl! "listen() failed")) + ;; Non-blocking accept + (set-nonblocking! fd) + ;; Get actual port + (let ((buf (foreign-alloc SOCKADDR_IN_SIZE)) + (len (foreign-alloc 4))) + (foreign-set! 'int len 0 SOCKADDR_IN_SIZE) + (c-getsockname fd buf len) + (let ((actual-port (sockaddr-in-port buf))) + (foreign-free buf) + (foreign-free len) + (set! *server-fd* fd) + (set! *server-port* actual-port) + (set! *server-running* #t) + (write-port-file! actual-port) + (fork-thread (lambda () (accept-loop fd))) + (log-info logger "started" + `((port . ,actual-port))) + actual-port))))) + +(def (stop-jcode-repl!) + "Stop the debug REPL if running." + (when *server-running* + (set! *server-running* #f) + (when *server-fd* + (guard (e [#t (void)]) + (c-close *server-fd*)) + (set! *server-fd* #f) + (set! *server-port* #f)) + (delete-port-file!))) + +(def (jcode-repl-port) + "Return the port the debug REPL is listening on, or #f." + *server-port*) --- a/src/jcode/provider/provider.ss +++ b/src/jcode/provider/provider.ss @@ -973,12 +973,10 @@ (let* ((status-line (port-read-line in)) (status (parse-http-status status-line)) (resp-headers (port-read-headers in)) - (te (header-value resp-headers "transfer-encoding")) - (raw (port-read-all-bytes in)) - (body-bv (if (and te (string-contains te "chunked")) - (decode-chunked-bytes raw) - raw)) - (body (utf8->string body-bv))) + (cl (header-value resp-headers "content-length")) + (body (if cl + (get-string-n in (string->number cl)) + (port-read-all in)))) (values status body))) (lambda () (close-port in) --- a/src/jcode/ui/cli.ss +++ b/src/jcode/ui/cli.ss @@ -10,6 +10,7 @@ :jcode/core/session :jcode/core/message :jcode/core/agent + :jcode/core/debug-repl :jcode/tool/registry :jcode/tool/file :jcode/tool/bash @@ -47,6 +48,11 @@ (load-config) (session-init-db) (init-tools) + ;; Start debug REPL if requested (--repl for auto-port, --repl-port N) + (let ((repl-opt (assoc '--repl-port opts))) + (when repl-opt + (let ((port (start-jcode-repl! (cdr repl-opt)))) + (printf "Debug REPL on port ~a (nc 127.0.0.1 ~a)~n" port port)))) ;; Apply CLI overrides to agent parameters (let ((p-opt (assoc '--provider opts)) (m-opt (assoc '--model opts))) @@ -78,6 +84,9 @@ ((equal? (car args) "--no-tui") (loop (cdr args) (cons '(--no-tui . #t) opts))) ((equal? (car args) "--no-mcp") (loop (cdr args) (cons '(--no-mcp . #t) opts))) ((equal? (car args) "--verbose") (loop (cdr args) (cons '(--verbose . #t) opts))) + ((and (equal? (car args) "--repl-port") (pair? (cdr args))) + (loop (cddr args) (cons (cons '--repl-port (string->number (cadr args))) opts))) + ((equal? (car args) "--repl") (loop (cdr args) (cons '(--repl-port . 0) opts))) ((and (equal? (car args) "--model") (pair? (cdr args))) (loop (cddr args) (cons (cons '--model (cadr args)) opts))) ((and (equal? (car args) "-m") (pair? (cdr args))) @@ -122,6 +131,8 @@ OPTIONS: --tui Launch terminal UI mode --no-tui Force line-mode REPL (default) --no-mcp Skip MCP server initialization + --repl Start debug REPL on auto-assigned port + --repl-port N Start debug REPL on specific port --verbose Log TUI events to ~/jcode.log COMMANDS: --- a/src/jcode/ui/tui.ss +++ b/src/jcode/ui/tui.ss @@ -19,6 +19,7 @@ :jcode/core/config :jcode/core/models :jcode/core/agent + :jcode/core/debug-repl :jcode/provider/provider :jcode/core/session :jcode/core/log @@ -176,13 +177,16 @@ (session-init-db) (init-tools-for-tui) (apply-tui-overrides! args) + ;; Start debug REPL (auto-port) — connect with: rlwrap nc 127.0.0.1 $(cat ~/.jcode-repl-port) + (let ((repl-port (start-jcode-repl!))) + (void)) (let ((verbose? (and (member "--verbose" args) #t))) (when verbose? (open-tui-log!)) (tui-log "tui-main: starting, args=~a" args) (redirect-stderr-for-tui! verbose?)) (with-tui (tui-log "tui-main: tb-init done") - (let ((in-mode (bitwise-ior TB_INPUT_ALT TB_INPUT_MOUSE))) + (let ((in-mode (bitwise-ior TB_INPUT_ESC TB_INPUT_MOUSE))) (tui-log "tui-main: setting input-mode=~a output-mode=~a" in-mode TB_OUTPUT_TRUECOLOR) (tb-set-input-mode! in-mode) (tb-set-output-mode! TB_OUTPUT_TRUECOLOR)) @@ -204,6 +208,7 @@ (*main-thread* (current-thread)) (tui-log "tui-main: entering event loop, main-thread=~a" (*main-thread*)) (event-loop state) + (stop-jcode-repl!) (restore-stderr!) (close-tui-log!)))) @@ -635,7 +640,14 @@ "Show a popup list of models for the current provider." (let* ((cur-p (or (current-provider-override) (config-provider))) (cur-m (or (current-model-override) (config-model))) - (models (provider-models cur-p)) + (models + ;; For Ollama, live-query the local API for installed models + (if (equal? cur-p "ollama") + (try + (let ((prov (make-provider "ollama" #f #f))) + (provider-list-models prov)) + (catch (e) (provider-models cur-p))) + (provider-models cur-p))) (items (map (lambda (m) (cons (format "~a~a (~a)" (if (equal? (car m) cur-m) "● " " ")