todo fix
ober
fccf434206fed4101ff4cd5a734bb651906a9579
--- a/docs/tools.md +++ b/docs/tools.md @@ -43,10 +43,15 @@ page is the catalogue plus the safety model that wraps every call. | Tool | Purpose | Parameters | |---|---|---| +| `todowrite` | Update the visible task plan in the TUI sidebar | `todos[]` of `{content,status,priority?}` | | `batch` | Run several tool calls in parallel (green threads) | `calls[]` of `{tool,args}` | | `task` | Spawn a sub-agent with its own context | `description`, `prompt`, `system?`, `agent?`, `task_id?` | | `repomap` | Condensed project map (top files by PageRank + key symbols) | *(none)* | +`todowrite` is for multi-step work. The model sends the complete current list +with statuses `pending`, `in_progress`, `completed`, or `cancelled`; the TUI +renders it on the right sidebar above the modified-file list. + `task`'s `system` argument can name a skill, so a sub-agent can be launched with a specialized prompt. LSP lookups (`lsp_definition` / `lsp_hover` / `lsp_references`) exist internally but are **not** exposed to the model — they --- a/docs/tui.md +++ b/docs/tui.md @@ -10,10 +10,11 @@ rendering, live diffs, themes, and a stats sidebar. ┌──────────────────────────────────────┬────────────────────┐ │ message thread │ sidebar (≥100 cols)│ │ user / assistant / tool blocks │ Sessions │ -│ markdown + syntax + diffs │ Files Changed │ -│ │ System (CPU/GPU) │ +│ markdown + syntax + diffs │ System (CPU/GPU) │ │ │ Tools │ │ ⠋ working… │ Connections │ +│ │ Todo │ +│ │ Modified Files │ ├──────────────────────────────────────┴────────────────────┤ │ › your input (wraps, up to 8 rows) │ ├────────────────────────────────────────────────────────────┤ @@ -24,10 +25,11 @@ rendering, live diffs, themes, and a stats sidebar. - **Message thread** — the conversation; the spinner row beneath it animates while the agent is working. - **Sidebar** — 24 columns, shown when the terminal is at least 100 wide. - Sections: **Sessions** (current marked `●`), **Files Changed** (git M/A/D), - **System** (CPU / MEM / GPU bars, or a stacked [memory breakdown](providers.md#hardware-tiers) - for local models), **Tools** (name + call count), **Connections** (MCP servers - and LSP status). + Sections: **Sessions** (current marked `●`), **System** (CPU / MEM / GPU + bars, or a stacked [memory breakdown](providers.md#hardware-tiers) for local + models), **Tools** (name + call count), **Connections** (MCP servers and LSP + status), **Todo** (the current `todowrite` plan), and **Modified Files** (git + M/A/D) at the bottom. - **Input** — multi-line, wraps to a maximum of 8 visible rows. - **Status bar** — mode (PLAN yellow / BUILD green), model, token counts (`in/out`, k/M-formatted), cache-hit rate, and accumulated cost. --- a/src/jcode/core/agent.ss +++ b/src/jcode/core/agent.ss @@ -53,7 +53,8 @@ (def *small-context-instruction-bytes* 3600) (def *small-context-tools* - '("apply_patch" "bash" "batch" "edit" "edit_block" "multi-edit" "patch" + '("todowrite" + "apply_patch" "bash" "batch" "edit" "edit_block" "multi-edit" "patch" "read" "write" "ls" "glob" "grep" "repomap" "git_status" "git_diff" "git_log" "git_show" "git_commit" "mcp_jerboa_jerboa")) @@ -236,8 +237,9 @@ dropped — emit a real tool_call message instead. When the user asks you to do something: 1. Use grep, glob, ls, or repomap to FIND the relevant source files for this repository 2. READ those files to see what's actually there -3. Make minimal targeted edits with the edit tool -4. Report back with results +3. For multi-step work, use todowrite to keep the visible todo list current; mark exactly one item in_progress when actively working. +4. Make minimal targeted edits with the edit tool +5. Report back with results Be concise. Prefer edit over write for modifying existing files. ~a" @@ -252,7 +254,7 @@ Be concise. Prefer edit over write for modifying existing files. (def (system-tool-section) (cond ((compact-agent-context?) - "Structured function tools are available through the API schema. Use exact schema arg names. Prefer ls/read/grep/glob/repomap to inspect, edit/apply_patch for changes, bash for verification, and git_status/git_diff/git_commit for commits.") + "Structured function tools are available through the API schema. Use exact schema arg names. Prefer todowrite for multi-step progress, ls/read/grep/glob/repomap to inspect, edit/apply_patch for changes, bash for verification, and git_status/git_diff/git_commit for commits.") (else (string-append "You have access to these tools:\n" new file mode 100644 --- /dev/null +++ b/src/jcode/tool/todo.ss @@ -0,0 +1,70 @@ +;;; jcode todo tool — plan/todo list updates for the TUI sidebar. + +(export init-todo-tool) + +(import :jcode/tool/registry + :jerboa/core + :jerboa/runtime) + +(def (init-todo-tool) + (register-tool! "todowrite" + (string-append + "Update the current task plan/todo list. Use this for multi-step work, " + "plans, and progress tracking. Each todo has content and status: " + "pending, in_progress, completed, or cancelled.") + (make-todo-schema) + handle-todowrite)) + +(def (make-todo-schema) + (let ((schema (make-hash-table)) + (props (make-hash-table)) + (todos-prop (make-hash-table)) + (items (make-hash-table)) + (item-props (make-hash-table)) + (content-prop (make-hash-table)) + (status-prop (make-hash-table)) + (priority-prop (make-hash-table))) + (hash-put! content-prop "type" "string") + (hash-put! content-prop "description" "Brief description of the task") + (hash-put! status-prop "type" "string") + (hash-put! status-prop "description" "pending, in_progress, completed, or cancelled") + (hash-put! priority-prop "type" "string") + (hash-put! priority-prop "description" "Optional priority: high, medium, or low") + (hash-put! item-props "content" content-prop) + (hash-put! item-props "status" status-prop) + (hash-put! item-props "priority" priority-prop) + (hash-put! items "type" "object") + (hash-put! items "properties" item-props) + (hash-put! items "required" '("content" "status")) + (hash-put! todos-prop "type" "array") + (hash-put! todos-prop "description" "The complete current todo list") + (hash-put! todos-prop "items" items) + (hash-put! props "todos" todos-prop) + (hash-put! schema "type" "object") + (hash-put! schema "properties" props) + (hash-put! schema "required" '("todos")) + schema)) + +(def (todo-status todo) + (if (hash-table? todo) + (hash-ref todo "status" "pending") + "pending")) + +(def (completed-status? status) + (or (equal? status "completed") + (equal? status "cancelled"))) + +(def (todo-summary todos) + (let* ((total (length todos)) + (done (length (filter (lambda (todo) + (completed-status? (todo-status todo))) + todos)))) + (format "Todo list updated: ~a of ~a complete" done total))) + +(def (handle-todowrite args) + (let ((todos (hash-ref args "todos" #f))) + (cond + ((not (list? todos)) + "Error: todowrite requires a todos array") + (else + (todo-summary todos))))) --- a/src/jcode/ui/cli.ss +++ b/src/jcode/ui/cli.ss @@ -22,6 +22,7 @@ :jcode/tool/file :jcode/tool/apply-patch :jcode/tool/bash + :jcode/tool/todo :jcode/tool/task :jcode/tool/repomap-tool :jcode/tool/web @@ -194,6 +195,7 @@ (init-file-tools) (init-apply-patch-tool) (init-bash-tool) + (init-todo-tool) (init-task-tool) (init-repomap-tool) (init-web-tools) --- a/src/jcode/ui/tui-sidebar.ss +++ b/src/jcode/ui/tui-sidebar.ss @@ -4,9 +4,10 @@ (export make-sidebar-state sidebar-state? make-fresh-sidebar sidebar-state-sessions-set! sidebar-state-files-set! + sidebar-state-todos-set! sidebar-state-tools-set! sidebar-state-mcp-set! sidebar-state-lsp-set! sidebar-state-current-session sidebar-state-sessions - sidebar-state-files sidebar-state-tools + sidebar-state-files sidebar-state-todos sidebar-state-tools sidebar-state-mcp sidebar-state-lsp render-sidebar!) @@ -23,13 +24,14 @@ (sessions ;; list of (id . title) current-session ;; id string files ;; list of (status . path) — from git status + todos ;; list of todo hash tables/alists from todowrite tools ;; list of (name . count) mcp ;; list of (name . tool-count) or '() lsp) ;; string name or #f transparent: #t) (def (make-fresh-sidebar) - (make-sidebar-state '() "" '() '() '() #f)) + (make-sidebar-state '() "" '() '() '() '() #f)) ;; ---- Rendering ---- @@ -65,9 +67,6 @@ ;; Sessions section (row (render-section! "Sessions" x y width max-row tfg tbg dfg bg #t)) (row (render-sessions! sidebar cx row cw max-row fg bg)) - ;; Files section - (row (render-section! "Files Changed" x row width max-row tfg tbg dfg bg #f)) - (row (render-files! sidebar cx row cw max-row fg bg)) ;; System section (above Tools so CPU/MEM/GPU stay visible) (row (render-section! "System" x row width max-row tfg tbg dfg bg #f)) (row (render-system! sysmon memstats cx row cw max-row fg bg)) @@ -75,8 +74,17 @@ (row (render-section! "Tools" x row width max-row tfg tbg dfg bg #f)) (row (render-tools! sidebar cx row cw max-row fg bg)) ;; Connections section - (row (render-section! "Connections" x row width max-row tfg tbg dfg bg #f))) - (render-connections! sidebar cx row cw max-row fg bg)) + (row (render-section! "Connections" x row width max-row tfg tbg dfg bg #f)) + (row (render-connections! sidebar cx row cw max-row fg bg)) + ;; Plan/todo and modified files live at the bottom. + (row (if (null? (sidebar-state-todos sidebar)) + row + (render-section! "Todo" x row width max-row tfg tbg dfg bg #f))) + (row (if (null? (sidebar-state-todos sidebar)) + row + (render-todos! sidebar cx row cw max-row fg bg))) + (row (render-section! "Modified Files" x row width max-row tfg tbg dfg bg #f))) + (render-files! sidebar cx row cw max-row fg bg)) (render-sidebar-bottom! x bottom-row width dfg bg))))) @@ -139,6 +147,47 @@ display)) (loop (cdr files) (+ row 1))))))) +(def (todo-field item key default) + (cond + ((hash-table? item) (hash-ref item key default)) + ((list? item) + (let ((hit (assoc (string->symbol key) item))) + (if hit (cdr hit) default))) + (else default))) + +(def (todo-done? status) + (or (equal? status "completed") + (equal? status "cancelled"))) + +(def (todo-marker status) + (cond + ((todo-done? status) "✓") + ((equal? status "in_progress") "•") + (else " "))) + +(def (fit-line s width) + (cond + ((<= width 0) "") + ((<= (string-length s) width) s) + ((<= width 1) "…") + (else (string-append (substring s 0 (- width 1)) "…")))) + +(def (render-todos! sidebar x row width max-row fg bg) + (let loop ((todos (sidebar-state-todos sidebar)) (row row)) + (cond + ((or (null? todos) (>= row max-row)) row) + (#t + (let* ((todo (car todos)) + (status (todo-field todo "status" "pending")) + (content (todo-field todo "content" "")) + (color (cond + ((equal? status "in_progress") (face-fg-attr 'status-cost)) + ((todo-done? status) (face-fg-attr 'sidebar-item)) + (else fg))) + (display (string-append "[" (todo-marker status) "] " content))) + (tb-print! x row color bg (fit-line display width)) + (loop (cdr todos) (+ row 1))))))) + (def (render-tools! sidebar x row width max-row fg bg) (let loop ((tools (sidebar-state-tools sidebar)) (row row)) (cond --- a/src/jcode/ui/tui.ss +++ b/src/jcode/ui/tui.ss @@ -34,6 +34,7 @@ :jcode/tool/file :jcode/tool/apply-patch :jcode/tool/bash + :jcode/tool/todo :jcode/tool/task :jcode/tool/repomap-tool :jcode/tool/web @@ -274,6 +275,7 @@ (init-file-tools) (init-apply-patch-tool) (init-bash-tool) + (init-todo-tool) (init-task-tool) (init-repomap-tool) (init-web-tools) @@ -700,7 +702,8 @@ (app-state-cache-creation-set! state 0) (app-state-cost-set! state 0.0) (app-state-tool-counts-set! state (make-hash-table)) - (app-state-active-tools-set! state '()))) + (app-state-active-tools-set! state '()) + (sidebar-state-todos-set! (app-state-sidebar state) '()))) ((equal? cmd "tools") (add-message! state (msg-block-system (string-append "Tools: " (string-join (list-tools) ", "))))) @@ -1981,12 +1984,15 @@ ;; Reset stream buffer so next round creates a fresh assistant block (finalize-last-assistant! state) (app-state-stream-buf-set! state "") - ;; Track tool counts in sidebar only — no inline message block - (let ((tc (app-state-tool-counts state))) - (hash-put! tc name (+ 1 (or (hash-get tc name) 0)))) - (app-state-active-tools-set! state - (cons name (app-state-active-tools state))) - (update-sidebar-tools! state) + (if (equal? name "todowrite") + (update-sidebar-todos! state args) + (begin + ;; Track tool counts in sidebar only — no inline message block + (let ((tc (app-state-tool-counts state))) + (hash-put! tc name (+ 1 (or (hash-get tc name) 0)))) + (app-state-active-tools-set! state + (cons name (app-state-active-tools state))) + (update-sidebar-tools! state))) (app-state-dirty?-set! state #t)) ((end) (app-state-active-tools-set! state @@ -2115,6 +2121,12 @@ (sidebar-state-tools-set! (app-state-sidebar state) (hash->list tc)))) +(def (update-sidebar-todos! state args) + (when (hash-table? args) + (let ((todos (hash-get args "todos"))) + (when (list? todos) + (sidebar-state-todos-set! (app-state-sidebar state) todos))))) + ;; ---- Theme cycling ---- (def (cycle-theme!) --- a/test/run.ss +++ b/test/run.ss @@ -16,6 +16,7 @@ (jcode tool registry) (jcode tool file) (jcode tool bash) + (jcode tool todo) (jcode tool external-llm) (jcode guardrails nudge) (jcode guardrails error-tracker) @@ -156,6 +157,7 @@ (current-log-level 'warn) (init-file-tools) (init-bash-tool) +(init-todo-tool) ;; ── Message tests ───────────────────────────────────────────────── @@ -277,6 +279,13 @@ (let ([r (tool-execute "bash" (args "command" "exit 42"))]) (check-pred! "bash non-zero exit shown" r (lambda (s) (str-contains? s "Exit code")))) +(let* ([todo1 (args "content" "Inspect sidebar" "status" "completed")] + [todo2 (args "content" "Wire todo list" "status" "in_progress")] + [r (tool-execute "todowrite" (args "todos" (list todo1 todo2)))]) + (check-pred! "todowrite progress summary" + r + (lambda (s) (str-contains? s "1 of 2 complete")))) + ;; ── TUI rendering ───────────────────────────────────────────────── (section "=== tui rendering ===")