Add local model do-it mode
ober
7c5bb3b28558cc3bfdabb9de66293deace0ffa57
--- a/docs/cli.md +++ b/docs/cli.md @@ -146,6 +146,7 @@ Available in the REPL and TUI. | `/plan` | Enter PLAN mode (read-only; write tools hidden). | | `/build` | Enter BUILD mode (read + write). | | `/mode` | Show the current mode. | +| `/doit [on\|off\|status\|max N\|max unlimited]` | Local-only auto-continue mode. When enabled on an `ollama*` or `mlx*` provider, jcode automatically sends a continuation prompt after each stopped assistant turn until the model marks the task done or blocked. | | `/clear` | Start a new session. | | `/sessions` | List saved sessions. | | `/compact` | Show the current session's message count. | --- a/src/jcode/core/agent.ss +++ b/src/jcode/core/agent.ss @@ -12,6 +12,9 @@ current-provider-override current-model-override current-compact-agent-context + current-do-it-mode? + current-do-it-max-continuations + do-it-terminal-response? get-current-provider forge-respond-enforced? forge-max-repeated-calls @@ -53,8 +56,24 @@ (def current-provider-override (make-parameter #f)) (def current-model-override (make-parameter #f)) (def current-compact-agent-context (make-parameter #f)) +(def current-do-it-mode? (make-parameter #f)) +(def current-do-it-max-continuations (make-parameter #f)) (def *small-context-instruction-bytes* 3600) +(def *do-it-done-marker* "[doit:done]") +(def *do-it-blocked-marker* "[doit:blocked]") +(def *do-it-continuation-prompt* + (string-append + "DO IT mode is still enabled. Continue the original task until it is " + "genuinely complete. Do not stop after finishing only one step. Use the " + "available tools to inspect, edit, test, and repair as needed.\n\n" + "If the task is fully complete, start your reply with exactly " + *do-it-done-marker* " and then give a concise final summary.\n" + "If you are genuinely blocked waiting for user input or external state, " + "start your reply with exactly " *do-it-blocked-marker* + " and state the blocker.")) +(def *do-it-stream-notice* + "\n\n[doit] model stopped; continuing automatically...\n\n") (def *small-context-tools* '("todowrite" @@ -240,6 +259,8 @@ Current mode: ~a ~a +~a + IMPORTANT RULES: - Follow this repository's instructions and actual file layout for source paths, generated artifacts, and build commands. More specific repo-local instructions, @@ -269,11 +290,23 @@ Be concise. Prefer edit over write for modifying existing files. (current-directory) (mode-label (current-mode)) (system-tool-section) + (do-it-instructions) (mode-instructions (current-mode)) (expert-prompt-instructions) (system-parallelism-rule) (agent-instructions-for-prompt))) +(def (do-it-instructions) + (if (current-do-it-mode?) + (string-append + "DO IT mode is ON for this local-model session. Keep working across " + "steps until the user's original task is fully complete. Do not present " + "a partial step as final. When the task is fully complete, start the " + "terminal reply with exactly " *do-it-done-marker* + ". If genuinely blocked waiting for the user or external state, start " + "the terminal reply with exactly " *do-it-blocked-marker* ".") + "DO IT mode is OFF.")) + (def (system-tool-section) (cond ((compact-agent-context?) @@ -1321,8 +1354,48 @@ Be concise. Prefer edit over write for modifying existing files. params) (json-object->string ht))) +(def (do-it-terminal-response? response) + (let ((content (and response (message-content response)))) + (and (string? content) + (or (string-contains content *do-it-done-marker*) + (string-contains content *do-it-blocked-marker*))))) + +(def (do-it-continue-again? response continuation-count) + (and (current-do-it-mode?) + (not (do-it-terminal-response? response)) + (let ((limit (current-do-it-max-continuations))) + (or (not limit) (< continuation-count limit))))) + +(def (emit-do-it-stream-notice!) + (let ((cb (current-stream-cb))) + (when cb (cb *do-it-stream-notice*)))) + (def (agent-run session-id user-input) - (log-info logger "agent-run" `((session . ,session-id))) + (log-info logger "agent-run" + `((session . ,session-id) + (do-it . ,(current-do-it-mode?)))) + (let ((provider (get-current-provider))) + (cond + ((and (current-do-it-mode?) + (not (local-provider? (provider-name provider)))) + (error 'agent-run + (format "DO IT mode is local-only; active provider is '~a'. Switch to a local provider (ollama/mlx) or run /doit off." + (provider-name provider)))) + ((current-do-it-mode?) + (agent-run-do-it session-id user-input 0)) + (else + (agent-run-once session-id user-input))))) + +(def (agent-run-do-it session-id user-input continuation-count) + (let ((response (agent-run-once session-id user-input))) + (if (do-it-continue-again? response continuation-count) + (begin + (emit-do-it-stream-notice!) + (agent-run-do-it session-id *do-it-continuation-prompt* + (+ continuation-count 1))) + response))) + +(def (agent-run-once session-id user-input) ;; Defensively repair the session before the new turn — a previously ;; cancelled or crashed turn may have left an assistant tool_calls ;; message without matching tool result messages, which the --- a/src/jcode/ui/cli.ss +++ b/src/jcode/ui/cli.ss @@ -16,6 +16,7 @@ :jcode/core/message :jcode/core/agent :jcode/core/expert + :jcode/core/models :jcode/core/errors :jcode/core/debug-repl :jcode/core/skill @@ -978,11 +979,67 @@ EXAMPLES: (unless (run-verified-task (get-current-provider) task bestof max-iterations vcmd cwd scope run-aliases? guidance-file json? status-file) (exit 1))))) +(def (do-it-provider-name) + (or (current-provider-override) (config-provider))) + +(def (do-it-max-label) + (let ((n (current-do-it-max-continuations))) + (if n (format "~a continuation(s)" n) "unlimited continuations"))) + +(def (print-do-it-status) + (printf "DO IT mode: ~a (~a, provider: ~a~a)~n" + (if (current-do-it-mode?) "ON" "OFF") + (do-it-max-label) + (do-it-provider-name) + (if (local-provider? (do-it-provider-name)) ", local" ", not local"))) + +(def (parse-do-it-max raw) + (let ((trimmed (string-trim raw))) + (cond + ((or (string=? trimmed "unlimited") + (string=? trimmed "none") + (string=? trimmed "off")) + #f) + (else + (let ((n (string->number trimmed))) + (unless (and n (integer? n) (exact? n) (>= n 0)) + (error 'doit "max requires a non-negative integer or 'unlimited'")) + n))))) + +(def (handle-do-it-command cmd) + (cond + ((or (equal? cmd "doit") (equal? cmd "doit status") + (equal? cmd "do-it") (equal? cmd "do-it status")) + (print-do-it-status)) + ((or (equal? cmd "doit on") (equal? cmd "do-it on")) + (let ((provider (do-it-provider-name))) + (if (local-provider? provider) + (begin + (current-do-it-mode? #t) + (printf "DO IT mode: ON for local provider ~a (~a). Use /doit off to stop.~n" + provider (do-it-max-label))) + (printf "DO IT mode refused: provider ~a is not local. Switch to ollama/mlx first.~n" + provider)))) + ((or (equal? cmd "doit off") (equal? cmd "do-it off")) + (current-do-it-mode? #f) + (printf "DO IT mode: OFF~n")) + ((or (string-prefix? "doit max " cmd) + (string-prefix? "do-it max " cmd)) + (let* ((prefix-len (if (string-prefix? "doit max " cmd) + (string-length "doit max ") + (string-length "do-it max "))) + (n (parse-do-it-max + (substring cmd prefix-len (string-length cmd))))) + (current-do-it-max-continuations n) + (printf "DO IT max: ~a~n" (do-it-max-label)))) + (else + (printf "Usage: /doit [on|off|status|max N| max unlimited]~n")))) + (def (handle-command input session-id) (let ((cmd (string-trim (substring input 1 (string-length input))))) (cond ((equal? cmd "help") - (display "\nCommands:\n /help Show this help\n /model [name] Show or set model\n /provider [name] Show or set provider\n /expert <prompt> Route one prompt to the configured expert model\n /plan Switch to PLAN mode (read-only)\n /build Switch to BUILD mode (read+write)\n /mode Show current mode\n /mcp Toggle MCP tools on/off\n /tools List available tools\n /agents List named sub-agent roles (task tool)\n /themes List available TUI themes\n /theme [name] Cycle or switch TUI theme\n /clear Start a new session\n /sessions List saved sessions\n /compact Show message count\n /undo [N] Revert last N checkpoint(s) (default 1)\n /checkpoints List recent shadow-git checkpoints\n /forge [on|off] Show or toggle forge guardrails\n /forge sampling <off|on|strict> Per-model sampling policy\n /forge workflow Describe + self-test the workflow engine\n /forge proxy Describe + self-test the OpenAI-compatible proxy\n /forge ablation Describe + self-test the eval/ablation harness\n /forge verify Describe + self-test the verify-gate (ATLAS verify+repair)\n /forge bestofk Describe + self-test best-of-k diverse-gen (ATLAS Phase-1)\n /forge breaker Describe + self-test the no-progress loop breaker\n /forge run [opts] <task> Verify-gated coding; opts: --verify, --bestof, --max-iterations, --write-scope, --guidance-file, --json, --status-file, --run-aliases\n /quit Exit\n\nMulti-line: end a line with \\ to continue on the next line.\n\n")) + (display "\nCommands:\n /help Show this help\n /model [name] Show or set model\n /provider [name] Show or set provider\n /expert <prompt> Route one prompt to the configured expert model\n /plan Switch to PLAN mode (read-only)\n /build Switch to BUILD mode (read+write)\n /mode Show current mode\n /doit [on|off|status|max N| max unlimited]\n Local-only auto-continue mode for long tasks\n /mcp Toggle MCP tools on/off\n /tools List available tools\n /agents List named sub-agent roles (task tool)\n /themes List available TUI themes\n /theme [name] Cycle or switch TUI theme\n /clear Start a new session\n /sessions List saved sessions\n /compact Show message count\n /undo [N] Revert last N checkpoint(s) (default 1)\n /checkpoints List recent shadow-git checkpoints\n /forge [on|off] Show or toggle forge guardrails\n /forge sampling <off|on|strict> Per-model sampling policy\n /forge workflow Describe + self-test the workflow engine\n /forge proxy Describe + self-test the OpenAI-compatible proxy\n /forge ablation Describe + self-test the eval/ablation harness\n /forge verify Describe + self-test the verify-gate (ATLAS verify+repair)\n /forge bestofk Describe + self-test best-of-k diverse-gen (ATLAS Phase-1)\n /forge breaker Describe + self-test the no-progress loop breaker\n /forge run [opts] <task> Verify-gated coding; opts: --verify, --bestof, --max-iterations, --write-scope, --guidance-file, --json, --status-file, --run-aliases\n /quit Exit\n\nMulti-line: end a line with \\ to continue on the next line.\n\n")) ((equal? cmd "model") (printf "Provider: ~a~n" (or (current-provider-override) (config-provider))) (printf "Model: ~a~n" (or (current-model-override) (config-model))) @@ -1046,7 +1103,11 @@ EXAMPLES: (printf "\x1b;[1;32mMode: BUILD\x1b;[0m (read+write)~n")) ((equal? cmd "mode") (printf "Current mode: ~a~n" - (if (eq? (current-mode) 'plan) "PLAN (read-only)" "BUILD (read+write)"))) + (if (eq? (current-mode) 'plan) "PLAN (read-only)" "BUILD (read+write)")) + (print-do-it-status)) + ((or (equal? cmd "doit") (string-prefix? "doit " cmd) + (equal? cmd "do-it") (string-prefix? "do-it " cmd)) + (handle-do-it-command cmd)) ((equal? cmd "clear") (let ((new-session (session-create "New session"))) (printf "Started new session.~n") --- a/src/jcode/ui/tui-input.ss +++ b/src/jcode/ui/tui-input.ss @@ -55,6 +55,16 @@ ("/mode" . "Show or set PLAN/BUILD mode") ("/plan" . "Switch to PLAN (read-only) mode") ("/build" . "Switch to BUILD (read+write) mode") + ("/doit" . "Local-only auto-continue mode") + ("/doit status" . "Show DO IT mode status") + ("/doit on" . "Enable DO IT mode for local providers") + ("/doit off" . "Disable DO IT mode") + ("/doit max" . "Set DO IT auto-continuation limit") + ("/do-it" . "Alias for /doit") + ("/do-it status" . "Show DO IT mode status") + ("/do-it on" . "Enable DO IT mode for local providers") + ("/do-it off" . "Disable DO IT mode") + ("/do-it max" . "Set DO IT auto-continuation limit") ("/clear" . "Start new session") ("/sessions" . "List saved sessions") ("/resume" . "Resume a stored session in this tab") --- a/src/jcode/ui/tui.ss +++ b/src/jcode/ui/tui.ss @@ -587,6 +587,68 @@ "Toggle between PLAN and BUILD." (set-mode! state (if (eq? (current-mode) 'plan) 'build 'plan))) +(def (do-it-provider-name) + (or (current-provider-override) (config-provider))) + +(def (do-it-max-label) + (let ((n (current-do-it-max-continuations))) + (if n (format "~a continuation(s)" n) "unlimited continuations"))) + +(def (do-it-status-message) + (format "DO IT mode: ~a (~a, provider: ~a~a)" + (if (current-do-it-mode?) "ON" "OFF") + (do-it-max-label) + (do-it-provider-name) + (if (local-provider? (do-it-provider-name)) ", local" ", not local"))) + +(def (parse-do-it-max raw) + (let ((trimmed (string-trim raw))) + (cond + ((or (string=? trimmed "unlimited") + (string=? trimmed "none") + (string=? trimmed "off")) + #f) + (else + (let ((n (string->number trimmed))) + (unless (and n (integer? n) (exact? n) (>= n 0)) + (error 'doit "max requires a non-negative integer or 'unlimited'")) + n))))) + +(def (handle-do-it-command! state cmd) + (cond + ((or (equal? cmd "doit") (equal? cmd "doit status") + (equal? cmd "do-it") (equal? cmd "do-it status")) + (add-message! state (msg-block-system (do-it-status-message)))) + ((or (equal? cmd "doit on") (equal? cmd "do-it on")) + (let ((provider (do-it-provider-name))) + (if (local-provider? provider) + (begin + (current-do-it-mode? #t) + (add-message! state + (msg-block-system + (format "DO IT mode: ON for local provider ~a (~a). Use /doit off to stop." + provider (do-it-max-label))))) + (add-message! state + (msg-block-system + (format "DO IT mode refused: provider ~a is not local. Switch to ollama/mlx first." + provider)))))) + ((or (equal? cmd "doit off") (equal? cmd "do-it off")) + (current-do-it-mode? #f) + (add-message! state (msg-block-system "DO IT mode: OFF"))) + ((or (string-prefix? "doit max " cmd) + (string-prefix? "do-it max " cmd)) + (let* ((prefix-len (if (string-prefix? "doit max " cmd) + (string-length "doit max ") + (string-length "do-it max "))) + (n (parse-do-it-max + (substring cmd prefix-len (string-length cmd))))) + (current-do-it-max-continuations n) + (add-message! state + (msg-block-system (format "DO IT max: ~a" (do-it-max-label)))))) + (else + (add-message! state + (msg-block-system "Usage: /doit [on|off|status|max N| max unlimited]"))))) + ;; ---- /resume ---- (def (messages->display-blocks msgs) @@ -659,6 +721,8 @@ " /plan Switch to PLAN mode (read-only)" " /build Switch to BUILD mode (read+write)" " /mode Show current mode" + " /doit [on|off|status|max N| max unlimited]" + " Local-only auto-continue mode for long tasks" " /tools List available tools" " /agents List named sub-agent roles (task tool)" " /clear Start new session" @@ -719,9 +783,15 @@ ((equal? cmd "mode") (add-message! state (msg-block-system - (if (eq? (current-mode) 'plan) - "Current mode: PLAN (read-only)" - "Current mode: BUILD (read+write)")))) + (string-append + (if (eq? (current-mode) 'plan) + "Current mode: PLAN (read-only)" + "Current mode: BUILD (read+write)") + "\n" + (do-it-status-message))))) + ((or (equal? cmd "doit") (string-prefix? "doit " cmd) + (equal? cmd "do-it") (string-prefix? "do-it " cmd)) + (handle-do-it-command! state cmd)) ((equal? cmd "model") (handle-model-popup! state)) ((equal? cmd "refresh-models")