Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .beads/.local_version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.29.0
0.46.0
3 changes: 2 additions & 1 deletion .beads/issues.jsonl
Original file line number Diff line number Diff line change
@@ -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"}
Expand Down Expand Up @@ -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"}
Expand Down
9 changes: 8 additions & 1 deletion lisp/core/efrit-executor.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down
10 changes: 6 additions & 4 deletions lisp/support/efrit-spinner.el
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)))))

Expand Down