diff --git a/.beads/.local_version b/.beads/.local_version index ae6dd4e..3010923 100644 --- a/.beads/.local_version +++ b/.beads/.local_version @@ -1 +1 @@ -0.29.0 +0.46.0 diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index d6809c2..7888cf5 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,6 +1,6 @@ {"id":"ef-088","title":"Add tool call navigation commands (M-n/M-p, copy)","description":"Add convenience commands for navigating between tool calls and interacting with them.\n\nIMPLEMENTATION:\n1. efrit-agent-next-tool (M-n)\n - Jump to next tool call in buffer\n - Use next-single-property-change on 'efrit-id\n\n2. efrit-agent-previous-tool (M-p)\n - Jump to previous tool call\n - Use previous-single-property-change on 'efrit-id\n\n3. efrit-agent-copy-tool-output (w key)\n - Copy the current tool's full output to kill ring\n - Works on tool at point\n\n4. Update efrit-agent-mode-map with new bindings\n\n5. Update help text in efrit-agent-help\n\nFILES: lisp/interfaces/efrit-agent.el","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-03T20:14:23.255864-08:00","updated_at":"2025-12-03T20:20:23.890023-08:00","closed_at":"2025-12-03T20:20:23.890023-08:00","dependencies":[{"issue_id":"ef-088","depends_on_id":"ef-3at","type":"parent-child","created_at":"2025-12-03T20:14:36.790062-08:00","created_by":"daemon"}]} {"id":"ef-0jt","title":"Expand/collapse all keybindings","description":"Add E keybinding to expand all tool results. Add C keybinding to collapse all. Update expansion state hash for all tools when used. These override Claude hints and display mode.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-03T17:50:39.955938-08:00","updated_at":"2025-12-03T18:33:55.597118-08:00","closed_at":"2025-12-03T18:33:55.597118-08:00","dependencies":[{"issue_id":"ef-0jt","depends_on_id":"ef-3i9","type":"blocks","created_at":"2025-12-03T17:50:53.773405-08:00","created_by":"daemon"}]} -{"id":"ef-0kr","title":"Redesign efrit interface: Replace command+progress model with interactive prompt loop","description":"","design":"Current UX model (fire-and-forget efrit-do):\n- User runs M-x efrit-do, types command once\n- Shows progress buffer with status\n- No way to ask follow-up questions or interact\n- Feels disconnected from what agent is doing\n\nTarget UX model (Amp/Claude Code style):\n- Single persistent \"Efrit Agent\" prompt buffer (REPL-like)\n- User types prompt at bottom \"\u003e \" cursor\n- Agent responses stream in above\n- Always ready for next prompt\n- IDE-like layout: prompt area at bottom, scrolling conversation/results above\n\nMerge architecture:\n- Unify efrit-chat, efrit-do, efrit-agent into single mode\n- Single well-designed buffer with regions:\n * Top: Scrolling output/conversation area\n * Middle: TODO/task list (fixed, refreshing)\n * Bottom: Input prompt (single line or minibuffer-like)\n- Session persistence and history\n- Tool results display inline with results\n- Clear visual distinction between:\n * User input\n * Agent responses/reasoning\n * Tool outputs\n * Errors/warnings","acceptance_criteria":"1. Single efrit-agent buffer with prompt-loop UX\n2. Can type multiple prompts without restarting\n3. Agent can ask for clarification (prompt user for input)\n4. IDE-like layout with fixed regions\n5. Streaming output visible as agent works\n6. Session history preserved\n7. Tool results formatted inline with conversation","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-04T11:36:51.064597-08:00","updated_at":"2025-12-04T18:59:37.763979-08:00","closed_at":"2025-12-04T18:59:37.763979-08:00"} +{"id":"ef-0kr","title":"Redesign efrit interface: Replace command+progress model with interactive prompt loop","design":"Current UX model (fire-and-forget efrit-do):\n- User runs M-x efrit-do, types command once\n- Shows progress buffer with status\n- No way to ask follow-up questions or interact\n- Feels disconnected from what agent is doing\n\nTarget UX model (Amp/Claude Code style):\n- Single persistent \"Efrit Agent\" prompt buffer (REPL-like)\n- User types prompt at bottom \"\u003e \" cursor\n- Agent responses stream in above\n- Always ready for next prompt\n- IDE-like layout: prompt area at bottom, scrolling conversation/results above\n\nMerge architecture:\n- Unify efrit-chat, efrit-do, efrit-agent into single mode\n- Single well-designed buffer with regions:\n * Top: Scrolling output/conversation area\n * Middle: TODO/task list (fixed, refreshing)\n * Bottom: Input prompt (single line or minibuffer-like)\n- Session persistence and history\n- Tool results display inline with results\n- Clear visual distinction between:\n * User input\n * Agent responses/reasoning\n * Tool outputs\n * Errors/warnings","acceptance_criteria":"1. Single efrit-agent buffer with prompt-loop UX\n2. Can type multiple prompts without restarting\n3. Agent can ask for clarification (prompt user for input)\n4. IDE-like layout with fixed regions\n5. Streaming output visible as agent works\n6. Session history preserved\n7. Tool results formatted inline with conversation","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-04T11:36:51.064597-08:00","updated_at":"2025-12-04T18:59:37.763979-08:00","closed_at":"2025-12-04T18:59:37.763979-08:00"} {"id":"ef-0pd","title":"DRY up tool handler boilerplate with macro/helper","description":"Many handlers in efrit-do-handlers.el follow identical patterns:\n\n```elisp\n(defun efrit-do--handle-FOO (tool-input)\n (require 'efrit-tool-foo)\n (or (efrit-do--validate-hash-table tool-input \"foo\")\n (efrit-do--validate-required tool-input \"foo\" \"field1\")\n (let* ((args (efrit-do--extract-fields tool-input '(\"field1\" \"field2\")))\n (result (efrit-tool-foo args)))\n (efrit-do--format-tool-result result \"Foo Result\"))))\n```\n\nThis is repeated for ~20 tools: web_search, fetch_url, read_file, edit_file, create_file, vcs_*, etc.\n\nCreate a macro or helper:\n\n```elisp\n(defmacro efrit-define-simple-tool-handler\n (fn-name tool-name required-fields field-specs underlying-fn label \u0026optional require-lib)\n ...)\n```\n\nThen replace boilerplate handlers with one-liners.\n\nFile affected:\n- lisp/interfaces/efrit-do-handlers.el (reduce from 879 to ~500 lines)","acceptance_criteria":"1. Macro defined for simple tool handlers\n2. At least 15 handlers converted to macro usage\n3. Easier to add new tools - single line per simple tool\n4. Byte-compile warnings unchanged or improved","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-02T21:58:07.240879-08:00","updated_at":"2025-12-02T23:14:18.369197-08:00","closed_at":"2025-12-02T23:14:18.369197-08:00"} {"id":"ef-0sa","title":"Agent buffer not automatically entering efrit-agent-mode","description":"When efrit-agent--get-buffer creates a new buffer, it doesn't automatically enter efrit-agent-mode. This means the buffer starts in fundamental-mode and lacks all the UI rendering, keybindings, and functionality provided by efrit-agent-mode.\n\nCurrent behavior: Buffer is created but doesn't have proper mode\nExpected behavior: Buffer should automatically enter efrit-agent-mode when created\n\nThis affects the user experience when invoking efrit-do/agentic mode - the agent buffer appears but without any of the special UI features like status display, task rendering, activity log, etc.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-03T17:26:00.180834-08:00","updated_at":"2025-12-03T17:26:36.265148-08:00","closed_at":"2025-12-03T17:26:36.265148-08:00"} {"id":"ef-0v9","title":"Add loading spinner to efrit-chat buffer during API calls","description":"Add a visual spinner or loading indicator in the chat buffer when waiting for API responses. Users perceive the application as unresponsive during slow API calls.","acceptance_criteria":"- Spinner appears in chat buffer when API call starts\n- Spinner disappears when response arrives\n- Spinner provides visual feedback that system is working","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-03T10:45:19.32003-08:00","updated_at":"2025-12-03T10:47:18.445578-08:00","closed_at":"2025-12-03T10:47:18.445578-08:00"} @@ -46,6 +46,7 @@ {"id":"ef-8o2","title":"Extract shared HTTP/API client layer (efrit-api.el)","description":"Both efrit-chat-api.el and efrit-executor.el duplicate HTTP request building logic:\n- JSON encoding with Unicode escaping\n- Header construction (x-api-key, anthropic-version, content-type)\n- url-retrieve / url-retrieve-synchronously calls\n- Response parsing\n\nCreate efrit-api.el (or extend efrit-common.el) with:\n```elisp\n(defun efrit-api-build-headers (api-key) ...)\n(defun efrit-api-request-async (request-data callback) ...)\n(defun efrit-api-request-sync (request-data \u0026optional timeout) ...)\n```\n\nAlso unify header customization:\n- efrit-custom-headers and efrit-excluded-headers are chat-only but should be shared\n\nFiles affected:\n- lisp/core/efrit-chat-api.el (efrit--build-headers, ~200 lines of HTTP code)\n- lisp/core/efrit-executor.el (efrit-executor--api-request, efrit-executor--sync-api-call)","acceptance_criteria":"1. Single efrit-api-request-async function used by both chat and executor\n2. Header customization (custom/excluded headers) works consistently in both modes\n3. No duplicated JSON encoding or Unicode escaping logic","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-02T21:58:07.180206-08:00","updated_at":"2025-12-02T22:37:37.783347-08:00","closed_at":"2025-12-02T22:37:37.783347-08:00"} {"id":"ef-8of","title":"Centralize tool-call rendering with efrit-agent-tool-view struct","description":"Currently tool call rendering is fragmented across three places:\n- efrit-agent--update-tool-result (initial result, collapsed line)\n- efrit-agent--expand-tool / efrit-agent--collapse-tool (toggle)\n- efrit-agent--apply-display-hint (rewriting header with summary/render-type/importance)\n\nAll hand-roll header lines with no single source of truth.\n\nIMPLEMENTATION:\n1. Define cl-defstruct efrit-agent-tool-view with fields:\n - id, name, input, result, success-p, elapsed, running\n - render-type, summary, importance, annotations, expanded-p\n\n2. Create efrit-agent--render-tool-call (start tv) function that:\n - Builds header line with expand/collapse icon based on expanded-p\n - Shows status icon/color based on success-p and importance\n - Shows tool name, elapsed time, summary\n - If expanded-p, appends Input/Result sections with proper formatting\n - Attaches all tool properties on the full region\n\n3. Refactor existing functions to use the new struct and renderer:\n - efrit-agent--update-tool-result\n - efrit-agent--apply-display-hint \n - efrit-agent--expand-tool / efrit-agent--collapse-tool\n\nThis fixes the current issue where \"auto-expanded\" items show expanded icon but no body.\n\nFILES: lisp/interfaces/efrit-agent-tools.el","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-03T20:14:23.219103-08:00","updated_at":"2025-12-03T20:18:49.445747-08:00","closed_at":"2025-12-03T20:18:49.445747-08:00","dependencies":[{"issue_id":"ef-8of","depends_on_id":"ef-3at","type":"blocks","created_at":"2025-12-03T20:14:23.22013-08:00","created_by":"daemon"}]} {"id":"ef-958","title":"Implement smart summary generation from annotations","description":"Currently collapsed headers show just truncated result text. Need context-sensitive summaries like \"3 files modified (+10/-3)\".\n\nIMPLEMENTATION:\n1. Define summary precedence in render function:\n - First: explicit summary from Claude's display_hint\n - Second: format from structured annotations\n - Third: fallback to truncated result\n\n2. Create efrit-agent--summary-from-annotations function that handles:\n - :kind diff-summary → \"3 files changed (+10/-4) [foo.py, bar.py]\"\n - :kind grep-summary → \"7 matches in 3 files for 'foo'\"\n - :kind test-summary → \"pytest: 1 failed, 23 passed (5.12s)\"\n - :kind shell-summary → \"make build (exit 0, 10 lines)\"\n - :kind fs-read-summary → \"Read /path/foo.py (120 lines)\"\n - :kind fs-write-summary → \"Wrote /path/foo.py (1024 bytes)\"\n\n3. Keep default-summary as robust fallback (truncated result text)\n\nNOTE: The annotation structures are provided by Claude. Emacs just formats them mechanically.\n\nFILES: lisp/interfaces/efrit-agent-tools.el","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-03T20:14:23.245202-08:00","updated_at":"2025-12-03T20:19:11.994228-08:00","closed_at":"2025-12-03T20:19:11.994228-08:00","dependencies":[{"issue_id":"ef-958","depends_on_id":"ef-8of","type":"blocks","created_at":"2025-12-03T20:14:36.739981-08:00","created_by":"daemon"},{"issue_id":"ef-958","depends_on_id":"ef-3at","type":"parent-child","created_at":"2025-12-03T20:14:36.787315-08:00","created_by":"daemon"}]} +{"id":"ef-959","title":"efrit-do async path fails with HTTP 400 after first call","description":"## Problem\n\nThe async `efrit-do` command fails on subsequent calls with HTTP 400 errors. The first call after clearing state works, but follow-up calls fail.\n\n## Symptoms\n\n```\n[ERROR] API request failed: Wrong type argument: stringp, nil\n[WARN] Session efrit-do-xxx: unknown stop reason: nil\n[ERROR] API request failed: HTTP error: (error http 400)\n```\n\n## Root Cause\n\nError handling mismatch between `efrit-executor--api-request` and `efrit-do-async--api-call`:\n\n1. When `efrit-executor--api-request` encounters an error, it calls `efrit-executor--handle-error` which passes an error STRING to the callback:\n ```elisp\n (funcall callback (format \"Error: %s\" message))\n ```\n\n2. But the callback in `efrit-do-async--api-call` (line 153-156 of efrit-do-async-loop.el) expects a response HASH-TABLE:\n ```elisp\n (lambda (response)\n (if (and response (efrit-response-error response))\n (funcall callback nil (efrit-error-message ...))\n (funcall callback response nil)))\n ```\n\n3. When the error string is passed, `efrit-response-error` returns nil (string is not hash-table), so it falls through to the else branch and treats the error string as a valid response.\n\n4. This causes `efrit-do-async--on-api-response` to receive a string instead of a response object, leading to nil content/stop_reason and the \"unknown stop reason\" warning.\n\n## Workaround\n\nUse `efrit-do-sync` instead - the synchronous path works correctly.\n\n## Files to Fix\n\n- `lisp/core/efrit-executor.el` - `efrit-executor--api-request` and `efrit-executor--handle-error`\n- `lisp/interfaces/efrit-do-async-loop.el` - `efrit-do-async--api-call` callback\n\n## Suggested Fix\n\nOption A: Make `efrit-executor--api-request` use separate success/error callbacks like `efrit-api-request-async` does.\n\nOption B: Have `efrit-executor--handle-error` create a fake response hash-table with an error field instead of passing a string.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-01-10T13:39:34.164711+01:00","created_by":"pierrebaille","updated_at":"2026-01-10T13:51:28.747354+01:00","closed_at":"2026-01-10T13:51:28.747354+01:00","close_reason":"Fixed by creating proper error response hash-table in efrit-executor--handle-error instead of passing raw string"} {"id":"ef-98f","title":"Polish: Tool result summary line truncates too aggressively in normal mode","description":"In normal display mode, the tool result summary on the collapsed line is quite short (appears to be ~40 chars based on the code). This sometimes cuts off useful context.\n\nConsider:\n- Increasing the limit for 'normal mode (maybe 60-80 chars)\n- Making truncation length configurable\n- Using window width to determine truncation\n\nCurrent values in efrit-agent--render-activity-item:\n- minimal: 20\n- normal: 40 \n- verbose: 80\n\nNot urgent, just a polish item.","status":"closed","priority":4,"issue_type":"chore","created_at":"2025-12-03T20:47:17.294294-08:00","updated_at":"2025-12-03T22:46:23.567133-08:00","closed_at":"2025-12-03T22:46:23.567133-08:00"} {"id":"ef-9cv","title":"Define efrit-repl-session struct","description":"Create a new session struct for REPL-style interaction:\n\n```elisp\n(cl-defstruct efrit-repl-session\n id ; Unique session ID\n conversation ; List of (role content timestamp)\n api-messages ; Vector for Claude API (accumulates)\n status ; idle/thinking/executing/waiting\n buffer ; Associated agent buffer\n created-at ; Session creation time\n last-activity ; For auto-save\n metadata) ; Project, title, etc.\n```\n\nKey difference from efrit-session:\n- Designed for multi-turn conversation\n- Never 'completes' - just pauses between turns\n- API messages accumulate properly","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-05T14:29:16.605953-08:00","updated_at":"2025-12-05T14:47:54.591908-08:00","closed_at":"2025-12-05T14:47:54.591908-08:00"} {"id":"ef-9ge","title":"Feature: Add Oracle tool for deep reasoning/planning","description":"Amp has an Oracle tool that invokes GPT-5 reasoning model for:\n- Code reviews and architecture feedback\n- Finding bugs across multiple files\n- Planning complex implementations\n- Analyzing code quality\n\nConsider adding a similar \"advisor\" tool that could invoke Claude's extended thinking mode for complex analysis tasks.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-05T16:10:37.058644-08:00","updated_at":"2025-12-05T16:15:50.021798-08:00","closed_at":"2025-12-05T16:15:50.021798-08:00"} diff --git a/lisp/core/efrit-executor.el b/lisp/core/efrit-executor.el index d0cf296..79d4c7f 100644 --- a/lisp/core/efrit-executor.el +++ b/lisp/core/efrit-executor.el @@ -148,7 +148,14 @@ Optionally calls CALLBACK with error message." (efrit-executor--clear-mode-line) (when callback - (funcall callback (format "Error: %s" message))) + ;; Create a proper error response hash-table so callbacks can detect + ;; the error using efrit-response-error instead of receiving a raw string + (let ((error-response (make-hash-table :test 'equal)) + (error-obj (make-hash-table :test 'equal))) + (puthash "type" "executor_error" error-obj) + (puthash "message" message error-obj) + (puthash "error" error-obj error-response) + (funcall callback error-response))) ;; Process next queued command if any (efrit-executor--process-queue))) diff --git a/lisp/support/efrit-spinner.el b/lisp/support/efrit-spinner.el index cfa3880..5329a50 100644 --- a/lisp/support/efrit-spinner.el +++ b/lisp/support/efrit-spinner.el @@ -23,9 +23,9 @@ ;;; Customization -(defcustom efrit-spinner-frames '("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏") +(defcustom efrit-spinner-frames ["⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏"] "Spinner animation frames to cycle through." - :type '(repeat string) + :type '(vector string) :group 'efrit) (defcustom efrit-spinner-interval 0.08 @@ -112,8 +112,10 @@ (goto-char (marker-position efrit-spinner--spinner-marker)) ;; Move back to the start of "System: " (beginning-of-line) - ;; Delete the entire line - (delete-region (point) (+ (point) (length "System: Thinking X\n"))))) + ;; Delete the entire line (to end of line + newline if present) + (let ((line-start (point))) + (forward-line 1) + (delete-region line-start (point))))) (setq-local efrit-spinner--spinner-marker nil) (setq-local efrit-spinner--frame-index 0)))))