agents
This commit is contained in:
parent
50c50b94cc
commit
6f18060a51
7 changed files with 1192 additions and 76 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -44,7 +44,7 @@ end
|
|||
|
||||
-- Create a new worktree
|
||||
function M.create_worktree(path, branch)
|
||||
branch = branch or 'main'
|
||||
branch = branch or M.default_branch()
|
||||
|
||||
-- Check if worktree already exists
|
||||
local worktrees = M.list_worktrees()
|
||||
|
|
@ -54,22 +54,44 @@ function M.create_worktree(path, branch)
|
|||
end
|
||||
end
|
||||
|
||||
-- Create worktree
|
||||
local cmd = string.format('git worktree add "%s" "%s" 2>&1', path, branch)
|
||||
-- Generate unique branch name for the worktree
|
||||
local worktree_branch = 'agent-' .. utils.timestamp()
|
||||
|
||||
-- Create worktree with new branch based on specified branch
|
||||
local cmd = string.format('git worktree add -b "%s" "%s" "%s" 2>&1', worktree_branch, path, branch)
|
||||
local result, err = utils.exec(cmd)
|
||||
|
||||
if err then
|
||||
return false, result
|
||||
end
|
||||
|
||||
return true, { path = path, branch = branch }
|
||||
-- For background agents, ensure no hooks by creating empty .claude directory
|
||||
-- This prevents inline diffs from triggering
|
||||
local claude_dir = path .. '/.claude'
|
||||
if vim.fn.isdirectory(claude_dir) == 0 then
|
||||
vim.fn.mkdir(claude_dir, 'p')
|
||||
end
|
||||
|
||||
-- Create empty settings.json to disable hooks
|
||||
local empty_settings = '{"hooks": {}}'
|
||||
utils.write_file(claude_dir .. '/settings.json', empty_settings)
|
||||
|
||||
return true, { path = path, branch = worktree_branch, base_branch = branch }
|
||||
end
|
||||
|
||||
-- Remove a worktree
|
||||
function M.remove_worktree(path)
|
||||
-- First try to remove as git worktree
|
||||
local cmd = string.format('git worktree remove "%s" --force 2>&1', path)
|
||||
local _, err = utils.exec(cmd)
|
||||
return err == nil
|
||||
local result, err = utils.exec(cmd)
|
||||
|
||||
-- If it's not a worktree or already removed, just delete the directory
|
||||
if err or result:match('not a working tree') then
|
||||
local rm_cmd = string.format('rm -rf "%s"', path)
|
||||
utils.exec(rm_cmd)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Add entry to .gitignore
|
||||
|
|
@ -103,6 +125,32 @@ function M.current_branch()
|
|||
return nil
|
||||
end
|
||||
|
||||
-- Get default branch (usually main or master)
|
||||
function M.default_branch()
|
||||
-- Try to get the default branch from remote
|
||||
local result = utils.exec('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null')
|
||||
if result and result ~= '' then
|
||||
local branch = result:match('refs/remotes/origin/(.+)')
|
||||
if branch then
|
||||
return branch:gsub('\n', '')
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: check if main or master exists
|
||||
local main_exists = utils.exec('git show-ref --verify --quiet refs/heads/main')
|
||||
if main_exists and main_exists == '' then
|
||||
return 'main'
|
||||
end
|
||||
|
||||
local master_exists = utils.exec('git show-ref --verify --quiet refs/heads/master')
|
||||
if master_exists and master_exists == '' then
|
||||
return 'master'
|
||||
end
|
||||
|
||||
-- Final fallback
|
||||
return 'main'
|
||||
end
|
||||
|
||||
-- Get git status
|
||||
function M.status(path)
|
||||
local cmd = 'git status --porcelain'
|
||||
|
|
|
|||
|
|
@ -20,16 +20,22 @@ end
|
|||
|
||||
-- Load registry from disk
|
||||
function M.load()
|
||||
vim.notify('registry.load() called', vim.log.levels.DEBUG)
|
||||
local content = utils.read_file(M.registry_path)
|
||||
if content then
|
||||
vim.notify(string.format('registry.load: Read %d bytes from %s', #content, M.registry_path), vim.log.levels.DEBUG)
|
||||
local ok, data = pcall(vim.json.decode, content)
|
||||
if ok and type(data) == 'table' then
|
||||
local agent_count = vim.tbl_count(data)
|
||||
vim.notify(string.format('registry.load: Decoded %d agents from JSON', agent_count), vim.log.levels.DEBUG)
|
||||
M.agents = data
|
||||
M.validate_agents()
|
||||
else
|
||||
vim.notify('registry.load: Failed to decode JSON, clearing agents', vim.log.levels.WARN)
|
||||
M.agents = {}
|
||||
end
|
||||
else
|
||||
vim.notify('registry.load: No content read from file, clearing agents', vim.log.levels.WARN)
|
||||
M.agents = {}
|
||||
end
|
||||
end
|
||||
|
|
@ -47,14 +53,27 @@ function M.validate_agents()
|
|||
local valid_agents = {}
|
||||
local now = os.time()
|
||||
|
||||
|
||||
for id, agent in pairs(M.agents) do
|
||||
-- Check if agent directory still exists
|
||||
if utils.file_exists(agent.work_dir .. '/mission.log') then
|
||||
local mission_log_path = agent.work_dir .. '/mission.log'
|
||||
local mission_exists = utils.file_exists(mission_log_path)
|
||||
|
||||
|
||||
if mission_exists then
|
||||
-- Check if tmux window still exists
|
||||
local window_exists = M.check_window_exists(agent.window_id)
|
||||
|
||||
if window_exists then
|
||||
agent.status = 'active'
|
||||
|
||||
-- Update progress from file for active agents
|
||||
local progress_file = agent.work_dir .. '/progress.txt'
|
||||
local progress_content = utils.read_file(progress_file)
|
||||
if progress_content and progress_content ~= '' then
|
||||
agent.progress = progress_content:gsub('\n$', '') -- Remove trailing newline
|
||||
end
|
||||
|
||||
valid_agents[id] = agent
|
||||
else
|
||||
-- Window closed, mark as completed
|
||||
|
|
@ -79,7 +98,7 @@ function M.check_window_exists(window_id)
|
|||
end
|
||||
|
||||
-- Register a new agent
|
||||
function M.register(task, work_dir, window_id, window_name)
|
||||
function M.register(task, work_dir, window_id, window_name, fork_info)
|
||||
local id = utils.timestamp() .. '-' .. math.random(1000, 9999)
|
||||
local agent = {
|
||||
id = id,
|
||||
|
|
@ -90,6 +109,9 @@ function M.register(task, work_dir, window_id, window_name)
|
|||
start_time = os.time(),
|
||||
status = 'active',
|
||||
project_root = utils.get_project_root(),
|
||||
progress = 'Starting...', -- Add progress field
|
||||
last_update = os.time(),
|
||||
fork_info = fork_info, -- Store branch/stash info
|
||||
}
|
||||
|
||||
M.agents[id] = agent
|
||||
|
|
@ -105,12 +127,19 @@ end
|
|||
|
||||
-- Get all agents for current project
|
||||
function M.get_project_agents()
|
||||
-- Ensure registry is loaded
|
||||
if not M.agents or vim.tbl_isempty(M.agents) then
|
||||
M.load()
|
||||
end
|
||||
|
||||
local project_root = utils.get_project_root()
|
||||
local project_agents = {}
|
||||
|
||||
for id, agent in pairs(M.agents) do
|
||||
if agent.project_root == project_root then
|
||||
project_agents[id] = agent
|
||||
-- Include the registry ID with the agent
|
||||
agent._registry_id = id
|
||||
table.insert(project_agents, agent)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -135,6 +164,16 @@ function M.update_status(id, status)
|
|||
if status == 'completed' or status == 'failed' then
|
||||
M.agents[id].end_time = os.time()
|
||||
end
|
||||
M.agents[id].last_update = os.time()
|
||||
M.save()
|
||||
end
|
||||
end
|
||||
|
||||
-- Update agent progress
|
||||
function M.update_progress(id, progress)
|
||||
if M.agents[id] then
|
||||
M.agents[id].progress = progress
|
||||
M.agents[id].last_update = os.time()
|
||||
M.save()
|
||||
end
|
||||
end
|
||||
|
|
@ -187,12 +226,22 @@ function M.format_agent(agent)
|
|||
age_str = string.format('%dd', math.floor(age / 86400))
|
||||
end
|
||||
|
||||
local progress_str = ''
|
||||
if agent.progress and agent.status == 'active' then
|
||||
progress_str = string.format(' | %s', agent.progress)
|
||||
end
|
||||
|
||||
-- Clean up task to single line
|
||||
local task_line = agent.task:match('[^\n]*') or agent.task
|
||||
local task_preview = task_line:sub(1, 50) .. (task_line:len() > 50 and '...' or '')
|
||||
|
||||
return string.format(
|
||||
'[%s] %s (%s) - %s',
|
||||
'[%s] %s (%s) - %s%s',
|
||||
agent.status:upper(),
|
||||
agent.task,
|
||||
task_preview,
|
||||
age_str,
|
||||
agent.window_name or 'unknown'
|
||||
agent.window_name or 'unknown',
|
||||
progress_str
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
67
lua/nvim-claude/statusline.lua
Normal file
67
lua/nvim-claude/statusline.lua
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
-- Statusline components for nvim-claude
|
||||
local M = {}
|
||||
|
||||
-- Get active agent count and summary
|
||||
function M.get_agent_status()
|
||||
local registry = require('nvim-claude.registry')
|
||||
|
||||
-- Validate agents to update their status
|
||||
registry.validate_agents()
|
||||
|
||||
local agents = registry.get_project_agents()
|
||||
local active_count = 0
|
||||
local latest_progress = nil
|
||||
local latest_task = nil
|
||||
|
||||
for _, agent in ipairs(agents) do
|
||||
if agent.status == 'active' then
|
||||
active_count = active_count + 1
|
||||
-- Get the most recently updated active agent
|
||||
if not latest_progress or (agent.last_update and agent.last_update > (latest_progress.last_update or 0)) then
|
||||
latest_progress = agent.progress
|
||||
latest_task = agent.task
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if active_count == 0 then
|
||||
return ''
|
||||
elseif active_count == 1 and latest_progress then
|
||||
-- Show single agent progress
|
||||
local task_short = latest_task
|
||||
if #latest_task > 20 then
|
||||
task_short = latest_task:sub(1, 17) .. '...'
|
||||
end
|
||||
return string.format('🤖 %s: %s', task_short, latest_progress)
|
||||
else
|
||||
-- Show count of multiple agents
|
||||
return string.format('🤖 %d agents', active_count)
|
||||
end
|
||||
end
|
||||
|
||||
-- Lualine component
|
||||
function M.lualine_component()
|
||||
return {
|
||||
M.get_agent_status,
|
||||
cond = function()
|
||||
-- Only show if there are active agents
|
||||
local status = M.get_agent_status()
|
||||
return status ~= ''
|
||||
end,
|
||||
on_click = function()
|
||||
-- Open agent list on click
|
||||
vim.cmd('ClaudeAgents')
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
-- Simple string function for custom statuslines
|
||||
function M.statusline()
|
||||
local status = M.get_agent_status()
|
||||
if status ~= '' then
|
||||
return ' ' .. status .. ' '
|
||||
end
|
||||
return ''
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -57,7 +57,7 @@ end
|
|||
-- Check if file exists
|
||||
function M.file_exists(path)
|
||||
local stat = vim.loop.fs_stat(path)
|
||||
return stat and stat.type == 'file'
|
||||
return stat ~= nil
|
||||
end
|
||||
|
||||
-- Generate timestamp string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue