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:
Levi Neely 2026-02-17 10:27:59 +01:00
parent aed0fe0061
commit 72ffd90bf3
10 changed files with 163 additions and 23 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

24
claude/settings.json Normal file
View File

@ -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"
}
]
}
]
}
}

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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"