Add Claude hooks integration for AnviLLM
- Created install-claude-hooks.sh to merge hooks into existing settings.json - Updated mkfile to use merge script instead of overwriting settings.json - Made agent name configurable via CLAUDE_AGENT_NAME env var - Added Claude agent configuration and hook scripts - Removed unused NewClaudeWithContinue/NewClaudeWithResume functions
This commit is contained in:
parent
aed0fe0061
commit
72ffd90bf3
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: anvillm-agent
|
||||
description: Agent for AnviLLM multi-agent workflows with Claude Code backend
|
||||
permissionMode: bypassPermissions
|
||||
---
|
||||
|
||||
You are an AI assistant for AnviLLM multi-agent workflows. You have full access to all tools and operate without permission restrictions.
|
||||
|
|
@ -4,7 +4,10 @@ filesystem:
|
|||
- "{HOME}/.claude"
|
||||
- "{HOME}/.claude.json"
|
||||
- "{HOME}/.config/anvillm"
|
||||
rwx:
|
||||
- "{HOME}/.claude/hooks"
|
||||
network:
|
||||
unrestricted: true
|
||||
env:
|
||||
- ANTHROPIC_API_KEY
|
||||
- CLAUDE_AGENT_NAME=anvillm-agent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
#!/bin/sh
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - Hook fired: Stop (PPID=$PPID)" >> /tmp/claude-hooks.log
|
||||
cat > /dev/null
|
||||
pid=$PPID
|
||||
while [ $pid -gt 1 ]; do
|
||||
if ps -p $pid -o comm= | grep -q claude; then
|
||||
break
|
||||
fi
|
||||
pid=$(ps -p $pid -o ppid= | tr -d ' ')
|
||||
done
|
||||
if [ $pid -le 1 ]; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: Claude PID not found" >> /tmp/claude-hooks.log
|
||||
exit 0
|
||||
fi
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - Found Claude PID: $pid" >> /tmp/claude-hooks.log
|
||||
# Read session ID from filesystem directly (9p read doesn't work in sandbox)
|
||||
ns_path="/tmp/ns.$USER.:0/anvillm-session-id-$pid"
|
||||
if [ ! -f "$ns_path" ]; then
|
||||
ns_path="/tmp/ns.$USER/anvillm-session-id-$pid"
|
||||
fi
|
||||
session=$(cat "$ns_path" 2>/dev/null)
|
||||
if [ -z "$session" ]; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: Session ID not found for PID $pid" >> /tmp/claude-hooks.log
|
||||
exit 0
|
||||
fi
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - Found session ID: $session" >> /tmp/claude-hooks.log
|
||||
if echo idle | 9p write agent/$session/state 2>>/tmp/claude-hooks.log; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: Wrote 'idle' to agent/$session/state" >> /tmp/claude-hooks.log
|
||||
else
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: Failed to write to agent/$session/state" >> /tmp/claude-hooks.log
|
||||
fi
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#!/bin/sh
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - Hook fired: UserPromptSubmit (PPID=$PPID)" >> /tmp/claude-hooks.log
|
||||
cat > /dev/null
|
||||
pid=$PPID
|
||||
while [ $pid -gt 1 ]; do
|
||||
if ps -p $pid -o comm= | grep -q claude; then
|
||||
break
|
||||
fi
|
||||
pid=$(ps -p $pid -o ppid= | tr -d ' ')
|
||||
done
|
||||
if [ $pid -le 1 ]; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: Claude PID not found" >> /tmp/claude-hooks.log
|
||||
exit 0
|
||||
fi
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - Found Claude PID: $pid" >> /tmp/claude-hooks.log
|
||||
# Read session ID from filesystem directly (9p read doesn't work in sandbox)
|
||||
ns_path="/tmp/ns.$USER.:0/anvillm-session-id-$pid"
|
||||
if [ ! -f "$ns_path" ]; then
|
||||
ns_path="/tmp/ns.$USER/anvillm-session-id-$pid"
|
||||
fi
|
||||
session=$(cat "$ns_path" 2>/dev/null)
|
||||
if [ -z "$session" ]; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: Session ID not found for PID $pid" >> /tmp/claude-hooks.log
|
||||
exit 0
|
||||
fi
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - Found session ID: $session" >> /tmp/claude-hooks.log
|
||||
if echo running | 9p write agent/$session/state 2>>/tmp/claude-hooks.log; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: Wrote 'running' to agent/$session/state" >> /tmp/claude-hooks.log
|
||||
else
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: Failed to write to agent/$session/state" >> /tmp/claude-hooks.log
|
||||
fi
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$HOME/.claude/hooks/anvillm-state-running.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$HOME/.claude/hooks/anvillm-state-idle.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -349,12 +349,15 @@ func (b *Backend) CreateSession(ctx context.Context, opts backend.SessionOptions
|
|||
}
|
||||
sess.idleCond = sync.NewCond(&sess.mu)
|
||||
|
||||
// Write session ID to file for hook access (if kiro-cli backend)
|
||||
if b.cfg.Name == "kiro-cli" && pid != 0 {
|
||||
// Write session ID to file for hook access (if kiro-cli or claude backend)
|
||||
if (b.cfg.Name == "kiro-cli" || b.cfg.Name == "claude") && pid != 0 {
|
||||
ns := client.Namespace()
|
||||
sessionIDFile := filepath.Join(ns, fmt.Sprintf("anvillm-session-id-%d", pid))
|
||||
os.WriteFile(sessionIDFile, []byte(id), 0644)
|
||||
debug.Log("[session %s] wrote session ID file: %s", id, sessionIDFile)
|
||||
if err := os.WriteFile(sessionIDFile, []byte(id), 0644); err != nil {
|
||||
debug.Log("[session %s] failed to write session ID file: %v", id, err)
|
||||
} else {
|
||||
debug.Log("[session %s] wrote session ID file: %s", id, sessionIDFile)
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Start reader goroutine
|
||||
|
|
|
|||
|
|
@ -25,20 +25,15 @@ func NewClaude(nsSuffix string) backend.Backend {
|
|||
return newClaudeWithCommand([]string{"claude", "--dangerously-skip-permissions"}, nsSuffix)
|
||||
}
|
||||
|
||||
// NewClaudeWithContinue creates a backend that continues the most recent conversation
|
||||
func NewClaudeWithContinue(nsSuffix string) backend.Backend {
|
||||
return newClaudeWithCommand([]string{"claude", "--dangerously-skip-permissions", "--continue"}, nsSuffix)
|
||||
}
|
||||
|
||||
// NewClaudeWithResume creates a backend that resumes a specific session by ID
|
||||
func NewClaudeWithResume(sessionID string, nsSuffix string) backend.Backend {
|
||||
return newClaudeWithCommand([]string{"claude", "--dangerously-skip-permissions", "--resume", sessionID}, nsSuffix)
|
||||
}
|
||||
|
||||
func newClaudeWithCommand(command []string, nsSuffix string) backend.Backend {
|
||||
agentName := os.Getenv("CLAUDE_AGENT_NAME")
|
||||
if agentName == "" {
|
||||
agentName = "anvillm-agent"
|
||||
}
|
||||
|
||||
return tmux.New(tmux.Config{
|
||||
Name: "claude",
|
||||
Command: command,
|
||||
Command: append(command, "--agent", agentName),
|
||||
Environment: map[string]string{
|
||||
"TERM": "xterm-256color",
|
||||
},
|
||||
|
|
@ -99,17 +94,12 @@ func (h *claudeCommands) Execute(ctx context.Context, command string) (string, e
|
|||
//
|
||||
// NOTE: Save/Load operations are NOT supported for Claude backend.
|
||||
// - Claude auto-saves all conversations to ~/.claude/projects/<dir>/<session-id>.jsonl
|
||||
// - Sessions are automatically continued via --agent hook integration
|
||||
// - No practical way to "load" context from one session into another
|
||||
// - Context sharing would require either:
|
||||
// 1. Expensive message replay (burns tokens, slow)
|
||||
// 2. Lossy summarization (defeats purpose of context sharing)
|
||||
// 3. File manipulation (risky, undefined behavior)
|
||||
//
|
||||
// For session management:
|
||||
// - Use NewClaudeWithResume(sessionID) to continue a specific session
|
||||
// - Use NewClaudeWithContinue() to continue most recent session
|
||||
// - Use ListSessions(cwd) to browse available sessions
|
||||
// Claude automatically saves conversations to ~/.claude/projects/<dir-path>/<session-id>.jsonl
|
||||
|
||||
// GetSessionDir returns the directory where Claude stores sessions for the given working directory
|
||||
func GetSessionDir(cwd string) string {
|
||||
|
|
|
|||
10
mkfile
10
mkfile
|
|
@ -16,8 +16,14 @@ build:V:
|
|||
chmod 0755 $INSTALL_PATH/Workflows/*
|
||||
mkdir -p $HOME/.config/anvillm
|
||||
cp -rf cfg/* $HOME/.config/anvillm/
|
||||
mkdir -p $HOME/.kiro/agents
|
||||
cp agents/* $HOME/.kiro/agents/
|
||||
mkdir -p $HOME/.kiro/agents/kiro-cli
|
||||
mkdir -p $HOME/.claude/agents
|
||||
mkdir -p $HOME/.claude/hooks
|
||||
cp agents/kiro-cli/* $HOME/.kiro/agents/kiro-cli/
|
||||
cp agents/claude/* $HOME/.claude/agents/
|
||||
cp claude/hooks/* $HOME/.claude/hooks/
|
||||
chmod +x $HOME/.claude/hooks/*.sh
|
||||
bash scripts/install-claude-hooks.sh
|
||||
|
||||
install:V: build
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
#!/bin/bash
|
||||
|
||||
SETTINGS_FILE="$HOME/.claude/settings.json"
|
||||
HOOKS_CONFIG='{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$HOME/.claude/hooks/anvillm-state-running.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$HOME/.claude/hooks/anvillm-state-idle.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}'
|
||||
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Error: jq is required but not installed. Please install jq first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/.claude"
|
||||
|
||||
if [ -f "$SETTINGS_FILE" ]; then
|
||||
# Merge with existing settings
|
||||
jq -s '.[0] * .[1]' "$SETTINGS_FILE" <(echo "$HOOKS_CONFIG") > "$SETTINGS_FILE.tmp"
|
||||
mv "$SETTINGS_FILE.tmp" "$SETTINGS_FILE"
|
||||
else
|
||||
# Create new settings file
|
||||
echo "$HOOKS_CONFIG" | jq '.' > "$SETTINGS_FILE"
|
||||
fi
|
||||
|
||||
echo "Claude hooks installed to $SETTINGS_FILE"
|
||||
Reference in New Issue