@tjamescouch/agentchat 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +11 -3
- package/lib/supervisor/USAGE.md +110 -0
- package/lib/supervisor/agent-supervisor.sh +135 -0
- package/lib/supervisor/agentctl.sh +250 -0
- package/lib/supervisor/killswitch.sh +36 -0
- package/lib/supervisor/notify.sh +19 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -43,16 +43,24 @@ export class AgentChatClient extends EventEmitter {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Load identity from file
|
|
46
|
+
* Load identity from file, or create new one if it doesn't exist
|
|
47
47
|
*/
|
|
48
48
|
async _loadIdentity() {
|
|
49
49
|
if (this.identityPath) {
|
|
50
50
|
try {
|
|
51
|
-
|
|
51
|
+
// Check if identity file exists
|
|
52
|
+
const exists = await Identity.exists(this.identityPath);
|
|
53
|
+
if (exists) {
|
|
54
|
+
this._identity = await Identity.load(this.identityPath);
|
|
55
|
+
} else {
|
|
56
|
+
// Generate new identity and save it
|
|
57
|
+
this._identity = Identity.generate(this.name);
|
|
58
|
+
await this._identity.save(this.identityPath);
|
|
59
|
+
}
|
|
52
60
|
this.name = this._identity.name;
|
|
53
61
|
this.pubkey = this._identity.pubkey;
|
|
54
62
|
} catch (err) {
|
|
55
|
-
throw new Error(`Failed to load identity
|
|
63
|
+
throw new Error(`Failed to load/create identity at ${this.identityPath}: ${err.message}`);
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Agent Supervisor System
|
|
2
|
+
|
|
3
|
+
Robust daemon management for Claude agents with automatic restart and state persistence.
|
|
4
|
+
|
|
5
|
+
## Why This Exists
|
|
6
|
+
|
|
7
|
+
Claude's built-in resume is unreliable. This system:
|
|
8
|
+
- Saves agent state externally to files
|
|
9
|
+
- Auto-restarts with exponential backoff
|
|
10
|
+
- Feeds previous context back on restart
|
|
11
|
+
- Lets agents save their own state before shutdown
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Add to PATH
|
|
17
|
+
export PATH="$PATH:$HOME/dev/claude/agentchat/lib/supervisor"
|
|
18
|
+
|
|
19
|
+
# Start an agent
|
|
20
|
+
agentctl start monitor "monitor agentchat #general, respond to messages, moderate spam"
|
|
21
|
+
|
|
22
|
+
# Check status
|
|
23
|
+
agentctl status
|
|
24
|
+
|
|
25
|
+
# View logs
|
|
26
|
+
agentctl logs monitor
|
|
27
|
+
|
|
28
|
+
# Stop gracefully
|
|
29
|
+
agentctl stop monitor
|
|
30
|
+
|
|
31
|
+
# Force kill
|
|
32
|
+
agentctl kill monitor
|
|
33
|
+
|
|
34
|
+
# Stop all agents
|
|
35
|
+
agentctl stopall
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Agent Self-Persistence
|
|
39
|
+
|
|
40
|
+
Inside your agent prompt, include instructions like:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
IMPORTANT: You are running under a supervisor that will restart you on failure.
|
|
44
|
+
|
|
45
|
+
Your state directory: ~/.agentchat/agents/YOUR_NAME/
|
|
46
|
+
- context.md: Save important state here BEFORE doing risky operations
|
|
47
|
+
- Read this file on startup to resume your work
|
|
48
|
+
|
|
49
|
+
Before any operation that might fail:
|
|
50
|
+
1. Write current task to context.md
|
|
51
|
+
2. Do the operation
|
|
52
|
+
3. Update context.md with result
|
|
53
|
+
|
|
54
|
+
On quota warnings or before shutdown:
|
|
55
|
+
- Save everything important to context.md
|
|
56
|
+
- Exit gracefully (the supervisor will restart you)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## File Structure
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
~/.agentchat/agents/
|
|
63
|
+
└── <agent-name>/
|
|
64
|
+
├── supervisor.pid # Supervisor process ID
|
|
65
|
+
├── state.json # Current state (managed by supervisor)
|
|
66
|
+
├── mission.txt # Original mission prompt
|
|
67
|
+
├── context.md # Agent-managed context (survives restarts)
|
|
68
|
+
└── supervisor.log # Supervisor logs
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Backoff Strategy
|
|
72
|
+
|
|
73
|
+
- Starts at 5 seconds
|
|
74
|
+
- Doubles on each failure (5 → 10 → 20 → 40 → 80 → 160 → 300)
|
|
75
|
+
- Caps at 5 minutes
|
|
76
|
+
- Resets if agent runs for >5 minutes before crashing
|
|
77
|
+
|
|
78
|
+
## Graceful Shutdown
|
|
79
|
+
|
|
80
|
+
Agents can check for stop signals:
|
|
81
|
+
```bash
|
|
82
|
+
# In bash
|
|
83
|
+
if [ -f ~/.agentchat/agents/YOUR_NAME/stop ]; then
|
|
84
|
+
echo "Shutdown requested, saving state..."
|
|
85
|
+
exit 0
|
|
86
|
+
fi
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Multiple Agents
|
|
90
|
+
|
|
91
|
+
You can run multiple specialized agents:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
agentctl start monitor "monitor agentchat, moderate, respond to questions"
|
|
95
|
+
agentctl start social "manage moltx/moltbook, post updates, engage with mentions"
|
|
96
|
+
agentctl start builder "work on assigned tasks from #tasks channel"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Viewing All State
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Status of all agents
|
|
103
|
+
agentctl status
|
|
104
|
+
|
|
105
|
+
# List registered agents
|
|
106
|
+
agentctl list
|
|
107
|
+
|
|
108
|
+
# View specific agent's saved context
|
|
109
|
+
agentctl context monitor
|
|
110
|
+
```
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Agent Supervisor - manages Claude agent lifecycle with automatic restart and backoff
|
|
3
|
+
# Usage: ./agent-supervisor.sh <agent-name> <mission>
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
AGENT_NAME="${1:-default}"
|
|
8
|
+
MISSION="${2:-monitor agentchat and respond to messages}"
|
|
9
|
+
STATE_DIR="$HOME/.agentchat/agents/$AGENT_NAME"
|
|
10
|
+
LOG_FILE="$STATE_DIR/supervisor.log"
|
|
11
|
+
PID_FILE="$STATE_DIR/supervisor.pid"
|
|
12
|
+
STOP_FILE="$STATE_DIR/stop"
|
|
13
|
+
STATE_FILE="$STATE_DIR/state.json"
|
|
14
|
+
|
|
15
|
+
# Backoff settings
|
|
16
|
+
MIN_BACKOFF=5
|
|
17
|
+
MAX_BACKOFF=300
|
|
18
|
+
BACKOFF_MULTIPLIER=2
|
|
19
|
+
|
|
20
|
+
mkdir -p "$STATE_DIR"
|
|
21
|
+
|
|
22
|
+
log() {
|
|
23
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
save_state() {
|
|
27
|
+
local status="$1"
|
|
28
|
+
local error="$2"
|
|
29
|
+
cat > "$STATE_FILE" << EOF
|
|
30
|
+
{
|
|
31
|
+
"agent_name": "$AGENT_NAME",
|
|
32
|
+
"mission": "$MISSION",
|
|
33
|
+
"status": "$status",
|
|
34
|
+
"last_error": "$error",
|
|
35
|
+
"restart_count": $RESTART_COUNT,
|
|
36
|
+
"started_at": "$STARTED_AT",
|
|
37
|
+
"updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
38
|
+
"pid": $$
|
|
39
|
+
}
|
|
40
|
+
EOF
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
cleanup() {
|
|
44
|
+
log "Supervisor shutting down"
|
|
45
|
+
save_state "stopped" ""
|
|
46
|
+
rm -f "$PID_FILE"
|
|
47
|
+
exit 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
trap cleanup SIGINT SIGTERM
|
|
51
|
+
|
|
52
|
+
# Check if already running
|
|
53
|
+
if [ -f "$PID_FILE" ]; then
|
|
54
|
+
OLD_PID=$(cat "$PID_FILE")
|
|
55
|
+
if ps -p "$OLD_PID" > /dev/null 2>&1; then
|
|
56
|
+
log "Supervisor already running (PID $OLD_PID)"
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
echo $$ > "$PID_FILE"
|
|
62
|
+
rm -f "$STOP_FILE"
|
|
63
|
+
|
|
64
|
+
RESTART_COUNT=0
|
|
65
|
+
BACKOFF=$MIN_BACKOFF
|
|
66
|
+
STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
67
|
+
|
|
68
|
+
log "Starting supervisor for agent '$AGENT_NAME'"
|
|
69
|
+
log "Mission: $MISSION"
|
|
70
|
+
save_state "starting" ""
|
|
71
|
+
|
|
72
|
+
while true; do
|
|
73
|
+
# Check for stop signal
|
|
74
|
+
if [ -f "$STOP_FILE" ]; then
|
|
75
|
+
log "Stop file detected, shutting down"
|
|
76
|
+
rm -f "$STOP_FILE"
|
|
77
|
+
cleanup
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
log "Starting agent (attempt $((RESTART_COUNT + 1)), backoff ${BACKOFF}s)"
|
|
81
|
+
save_state "running" ""
|
|
82
|
+
|
|
83
|
+
# Build the resume prompt with state context
|
|
84
|
+
RESUME_PROMPT="You are agent '$AGENT_NAME'. Your mission: $MISSION
|
|
85
|
+
|
|
86
|
+
IMPORTANT: You are being restarted by a supervisor. Check your state file at:
|
|
87
|
+
$STATE_FILE
|
|
88
|
+
|
|
89
|
+
Read ~/.agentchat/agents/$AGENT_NAME/context.md for any saved context from your previous run.
|
|
90
|
+
Before doing significant work, save your current task to context.md so you can resume if interrupted.
|
|
91
|
+
|
|
92
|
+
On quota errors or before shutdown, write your current state to context.md.
|
|
93
|
+
|
|
94
|
+
Begin your mission now."
|
|
95
|
+
|
|
96
|
+
# Run claude with the mission
|
|
97
|
+
START_TIME=$(date +%s)
|
|
98
|
+
|
|
99
|
+
if claude -p "$RESUME_PROMPT" 2>> "$LOG_FILE"; then
|
|
100
|
+
# Clean exit
|
|
101
|
+
log "Agent exited cleanly"
|
|
102
|
+
BACKOFF=$MIN_BACKOFF
|
|
103
|
+
else
|
|
104
|
+
EXIT_CODE=$?
|
|
105
|
+
END_TIME=$(date +%s)
|
|
106
|
+
DURATION=$((END_TIME - START_TIME))
|
|
107
|
+
|
|
108
|
+
log "Agent crashed (exit code $EXIT_CODE, ran for ${DURATION}s)"
|
|
109
|
+
save_state "crashed" "exit_code=$EXIT_CODE"
|
|
110
|
+
|
|
111
|
+
# If it ran for more than 5 minutes, reset backoff
|
|
112
|
+
if [ $DURATION -gt 300 ]; then
|
|
113
|
+
BACKOFF=$MIN_BACKOFF
|
|
114
|
+
log "Ran long enough, resetting backoff"
|
|
115
|
+
else
|
|
116
|
+
# Exponential backoff
|
|
117
|
+
BACKOFF=$((BACKOFF * BACKOFF_MULTIPLIER))
|
|
118
|
+
if [ $BACKOFF -gt $MAX_BACKOFF ]; then
|
|
119
|
+
BACKOFF=$MAX_BACKOFF
|
|
120
|
+
fi
|
|
121
|
+
fi
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
RESTART_COUNT=$((RESTART_COUNT + 1))
|
|
125
|
+
|
|
126
|
+
# Check for stop signal before sleeping
|
|
127
|
+
if [ -f "$STOP_FILE" ]; then
|
|
128
|
+
log "Stop file detected, shutting down"
|
|
129
|
+
rm -f "$STOP_FILE"
|
|
130
|
+
cleanup
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
log "Waiting ${BACKOFF}s before restart..."
|
|
134
|
+
sleep $BACKOFF
|
|
135
|
+
done
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# agentctl - manage supervised Claude agents
|
|
3
|
+
# Usage: agentctl <command> [agent-name] [options]
|
|
4
|
+
|
|
5
|
+
AGENTS_DIR="$HOME/.agentchat/agents"
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")")" && pwd)"
|
|
7
|
+
SUPERVISOR_SCRIPT="$SCRIPT_DIR/agent-supervisor.sh"
|
|
8
|
+
|
|
9
|
+
usage() {
|
|
10
|
+
cat << EOF
|
|
11
|
+
Usage: agentctl <command> [agent-name] [options]
|
|
12
|
+
|
|
13
|
+
Commands:
|
|
14
|
+
start <name> <mission> Start a new supervised agent
|
|
15
|
+
stop <name> Stop an agent gracefully
|
|
16
|
+
kill <name> Force kill an agent
|
|
17
|
+
restart <name> Restart an agent
|
|
18
|
+
status [name] Show agent status (all if no name)
|
|
19
|
+
logs <name> [lines] Show agent logs
|
|
20
|
+
list List all agents
|
|
21
|
+
context <name> Show agent's saved context
|
|
22
|
+
stopall Stop all agents
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
agentctl start monitor "monitor agentchat #general and moderate"
|
|
26
|
+
agentctl start social "manage moltx and moltbook social media"
|
|
27
|
+
agentctl stop monitor
|
|
28
|
+
agentctl status
|
|
29
|
+
EOF
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
start_agent() {
|
|
33
|
+
local name="$1"
|
|
34
|
+
local mission="$2"
|
|
35
|
+
|
|
36
|
+
if [ -z "$name" ] || [ -z "$mission" ]; then
|
|
37
|
+
echo "Usage: agentctl start <name> <mission>"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
local state_dir="$AGENTS_DIR/$name"
|
|
42
|
+
mkdir -p "$state_dir"
|
|
43
|
+
|
|
44
|
+
# Check if already running
|
|
45
|
+
if [ -f "$state_dir/supervisor.pid" ]; then
|
|
46
|
+
local pid=$(cat "$state_dir/supervisor.pid")
|
|
47
|
+
if ps -p "$pid" > /dev/null 2>&1; then
|
|
48
|
+
echo "Agent '$name' already running (PID $pid)"
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Save mission for restarts
|
|
54
|
+
echo "$mission" > "$state_dir/mission.txt"
|
|
55
|
+
|
|
56
|
+
# Initialize context file
|
|
57
|
+
if [ ! -f "$state_dir/context.md" ]; then
|
|
58
|
+
cat > "$state_dir/context.md" << EOF
|
|
59
|
+
# Agent: $name
|
|
60
|
+
## Mission
|
|
61
|
+
$mission
|
|
62
|
+
|
|
63
|
+
## Current State
|
|
64
|
+
Starting fresh.
|
|
65
|
+
|
|
66
|
+
## Notes
|
|
67
|
+
(Save important context here before shutdown)
|
|
68
|
+
EOF
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
echo "Starting agent '$name'..."
|
|
72
|
+
nohup "$SUPERVISOR_SCRIPT" "$name" "$mission" > /dev/null 2>&1 &
|
|
73
|
+
echo "Agent '$name' started (supervisor PID $!)"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
stop_agent() {
|
|
77
|
+
local name="$1"
|
|
78
|
+
local state_dir="$AGENTS_DIR/$name"
|
|
79
|
+
|
|
80
|
+
if [ ! -d "$state_dir" ]; then
|
|
81
|
+
echo "Agent '$name' not found"
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Create stop file for graceful shutdown
|
|
86
|
+
touch "$state_dir/stop"
|
|
87
|
+
echo "Stop signal sent to '$name'"
|
|
88
|
+
|
|
89
|
+
# Wait a moment then check
|
|
90
|
+
sleep 2
|
|
91
|
+
if [ -f "$state_dir/supervisor.pid" ]; then
|
|
92
|
+
local pid=$(cat "$state_dir/supervisor.pid")
|
|
93
|
+
if ps -p "$pid" > /dev/null 2>&1; then
|
|
94
|
+
echo "Agent still running, waiting..."
|
|
95
|
+
sleep 5
|
|
96
|
+
if ps -p "$pid" > /dev/null 2>&1; then
|
|
97
|
+
echo "Agent didn't stop gracefully, use 'agentctl kill $name'"
|
|
98
|
+
fi
|
|
99
|
+
else
|
|
100
|
+
echo "Agent '$name' stopped"
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
kill_agent() {
|
|
106
|
+
local name="$1"
|
|
107
|
+
local state_dir="$AGENTS_DIR/$name"
|
|
108
|
+
|
|
109
|
+
if [ ! -d "$state_dir" ]; then
|
|
110
|
+
echo "Agent '$name' not found"
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if [ -f "$state_dir/supervisor.pid" ]; then
|
|
115
|
+
local pid=$(cat "$state_dir/supervisor.pid")
|
|
116
|
+
if ps -p "$pid" > /dev/null 2>&1; then
|
|
117
|
+
# Kill the supervisor and its children
|
|
118
|
+
pkill -P "$pid" 2>/dev/null
|
|
119
|
+
kill "$pid" 2>/dev/null
|
|
120
|
+
rm -f "$state_dir/supervisor.pid"
|
|
121
|
+
echo "Agent '$name' killed"
|
|
122
|
+
else
|
|
123
|
+
echo "Agent '$name' not running"
|
|
124
|
+
rm -f "$state_dir/supervisor.pid"
|
|
125
|
+
fi
|
|
126
|
+
else
|
|
127
|
+
echo "No PID file for '$name'"
|
|
128
|
+
fi
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
show_status() {
|
|
132
|
+
local name="$1"
|
|
133
|
+
|
|
134
|
+
if [ -n "$name" ]; then
|
|
135
|
+
local state_dir="$AGENTS_DIR/$name"
|
|
136
|
+
if [ -f "$state_dir/state.json" ]; then
|
|
137
|
+
cat "$state_dir/state.json" | python3 -m json.tool 2>/dev/null || cat "$state_dir/state.json"
|
|
138
|
+
else
|
|
139
|
+
echo "No state file for '$name'"
|
|
140
|
+
fi
|
|
141
|
+
else
|
|
142
|
+
echo "=== Agent Status ==="
|
|
143
|
+
for dir in "$AGENTS_DIR"/*/; do
|
|
144
|
+
if [ -d "$dir" ]; then
|
|
145
|
+
local agent=$(basename "$dir")
|
|
146
|
+
local status="unknown"
|
|
147
|
+
local pid=""
|
|
148
|
+
|
|
149
|
+
if [ -f "$dir/state.json" ]; then
|
|
150
|
+
status=$(python3 -c "import json; print(json.load(open('$dir/state.json')).get('status', 'unknown'))" 2>/dev/null || echo "unknown")
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
if [ -f "$dir/supervisor.pid" ]; then
|
|
154
|
+
pid=$(cat "$dir/supervisor.pid")
|
|
155
|
+
if ! ps -p "$pid" > /dev/null 2>&1; then
|
|
156
|
+
status="dead"
|
|
157
|
+
pid=""
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
printf "%-15s %-10s %s\n" "$agent" "$status" "${pid:+PID $pid}"
|
|
162
|
+
fi
|
|
163
|
+
done
|
|
164
|
+
fi
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
show_logs() {
|
|
168
|
+
local name="$1"
|
|
169
|
+
local lines="${2:-50}"
|
|
170
|
+
local log_file="$AGENTS_DIR/$name/supervisor.log"
|
|
171
|
+
|
|
172
|
+
if [ -f "$log_file" ]; then
|
|
173
|
+
tail -n "$lines" "$log_file"
|
|
174
|
+
else
|
|
175
|
+
echo "No logs for '$name'"
|
|
176
|
+
fi
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
list_agents() {
|
|
180
|
+
echo "=== Registered Agents ==="
|
|
181
|
+
for dir in "$AGENTS_DIR"/*/; do
|
|
182
|
+
if [ -d "$dir" ]; then
|
|
183
|
+
local agent=$(basename "$dir")
|
|
184
|
+
local mission=""
|
|
185
|
+
if [ -f "$dir/mission.txt" ]; then
|
|
186
|
+
mission=$(cat "$dir/mission.txt")
|
|
187
|
+
fi
|
|
188
|
+
echo "$agent: $mission"
|
|
189
|
+
fi
|
|
190
|
+
done
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
show_context() {
|
|
194
|
+
local name="$1"
|
|
195
|
+
local context_file="$AGENTS_DIR/$name/context.md"
|
|
196
|
+
|
|
197
|
+
if [ -f "$context_file" ]; then
|
|
198
|
+
cat "$context_file"
|
|
199
|
+
else
|
|
200
|
+
echo "No context file for '$name'"
|
|
201
|
+
fi
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
stop_all() {
|
|
205
|
+
echo "Stopping all agents..."
|
|
206
|
+
for dir in "$AGENTS_DIR"/*/; do
|
|
207
|
+
if [ -d "$dir" ]; then
|
|
208
|
+
local agent=$(basename "$dir")
|
|
209
|
+
touch "$dir/stop"
|
|
210
|
+
echo "Stop signal sent to '$agent'"
|
|
211
|
+
fi
|
|
212
|
+
done
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# Main
|
|
216
|
+
case "$1" in
|
|
217
|
+
start)
|
|
218
|
+
start_agent "$2" "$3"
|
|
219
|
+
;;
|
|
220
|
+
stop)
|
|
221
|
+
stop_agent "$2"
|
|
222
|
+
;;
|
|
223
|
+
kill)
|
|
224
|
+
kill_agent "$2"
|
|
225
|
+
;;
|
|
226
|
+
restart)
|
|
227
|
+
stop_agent "$2"
|
|
228
|
+
sleep 3
|
|
229
|
+
mission=$(cat "$AGENTS_DIR/$2/mission.txt" 2>/dev/null)
|
|
230
|
+
start_agent "$2" "$mission"
|
|
231
|
+
;;
|
|
232
|
+
status)
|
|
233
|
+
show_status "$2"
|
|
234
|
+
;;
|
|
235
|
+
logs)
|
|
236
|
+
show_logs "$2" "$3"
|
|
237
|
+
;;
|
|
238
|
+
list)
|
|
239
|
+
list_agents
|
|
240
|
+
;;
|
|
241
|
+
context)
|
|
242
|
+
show_context "$2"
|
|
243
|
+
;;
|
|
244
|
+
stopall)
|
|
245
|
+
stop_all
|
|
246
|
+
;;
|
|
247
|
+
*)
|
|
248
|
+
usage
|
|
249
|
+
;;
|
|
250
|
+
esac
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Kill switch checker - stops all agents if kill file exists
|
|
3
|
+
# Kill file locations (check any of these):
|
|
4
|
+
# 1. iCloud: ~/Library/Mobile Documents/com~apple~CloudDocs/KILL_AGENTS
|
|
5
|
+
# 2. Local: ~/.agentchat/KILL
|
|
6
|
+
# 3. Dropbox: ~/Dropbox/KILL_AGENTS (if exists)
|
|
7
|
+
|
|
8
|
+
ICLOUD_KILL="$HOME/Library/Mobile Documents/com~apple~CloudDocs/KILL_AGENTS"
|
|
9
|
+
LOCAL_KILL="$HOME/.agentchat/KILL"
|
|
10
|
+
DROPBOX_KILL="$HOME/Dropbox/KILL_AGENTS"
|
|
11
|
+
|
|
12
|
+
check_kill() {
|
|
13
|
+
if [ -f "$ICLOUD_KILL" ] || [ -f "$LOCAL_KILL" ] || [ -f "$DROPBOX_KILL" ]; then
|
|
14
|
+
return 0 # Kill signal found
|
|
15
|
+
fi
|
|
16
|
+
return 1 # No kill signal
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if check_kill; then
|
|
20
|
+
echo "KILL SIGNAL DETECTED"
|
|
21
|
+
echo "Stopping all agents..."
|
|
22
|
+
|
|
23
|
+
# Stop all supervised agents
|
|
24
|
+
"$HOME/bin/agentctl" stopall 2>/dev/null
|
|
25
|
+
|
|
26
|
+
# Kill any claude processes
|
|
27
|
+
pkill -f "claude" 2>/dev/null
|
|
28
|
+
|
|
29
|
+
# Clean up kill files
|
|
30
|
+
rm -f "$ICLOUD_KILL" "$LOCAL_KILL" "$DROPBOX_KILL" 2>/dev/null
|
|
31
|
+
|
|
32
|
+
echo "All agents terminated."
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
exit 0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Send notification to phone via ntfy.sh (free, no signup needed)
|
|
3
|
+
# Usage: notify.sh "title" "message" [priority]
|
|
4
|
+
# Priority: 1=min, 2=low, 3=default, 4=high, 5=urgent
|
|
5
|
+
|
|
6
|
+
TOPIC="agentchat-james-$(whoami | md5sum | cut -c1-8)" # Unique topic
|
|
7
|
+
TITLE="${1:-Agent Alert}"
|
|
8
|
+
MESSAGE="${2:-Something happened}"
|
|
9
|
+
PRIORITY="${3:-3}"
|
|
10
|
+
|
|
11
|
+
# Send via ntfy.sh
|
|
12
|
+
curl -s \
|
|
13
|
+
-H "Title: $TITLE" \
|
|
14
|
+
-H "Priority: $PRIORITY" \
|
|
15
|
+
-H "Tags: robot" \
|
|
16
|
+
-d "$MESSAGE" \
|
|
17
|
+
"https://ntfy.sh/$TOPIC" > /dev/null
|
|
18
|
+
|
|
19
|
+
echo "Notification sent to ntfy.sh/$TOPIC"
|