Initial commit with translated description
This commit is contained in:
135
README.md
Normal file
135
README.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# Task Status Skill
|
||||||
|
|
||||||
|
A Clawdbot skill for sending short status descriptions in chat, with automatic periodic monitoring that updates every 5 seconds.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Install into Clawdbot
|
||||||
|
```bash
|
||||||
|
# Copy to Clawdbot skills directory
|
||||||
|
copy task-status "C:\Users\Luffy\AppData\Roaming\npm\node_modules\clawdbot\skills\task-status"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
#### Manual Updates (Single Status Messages)
|
||||||
|
```bash
|
||||||
|
python scripts/send_status.py "Still working..." "progress" "task_name"
|
||||||
|
python scripts/send_status.py "Complete!" "success" "task_name"
|
||||||
|
python scripts/send_status.py "Error!" "error" "task_name"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Automatic Periodic Monitoring (Every 5 seconds)
|
||||||
|
```bash
|
||||||
|
# Start monitoring before your long task
|
||||||
|
python scripts/monitor_task.py start "my_long_task" "progress"
|
||||||
|
|
||||||
|
# Your long running task/process here...
|
||||||
|
# Monitor sends "Still working..." updates every 5 seconds automatically
|
||||||
|
|
||||||
|
# Stop monitoring with final status
|
||||||
|
python scripts/monitor_task.py stop "my_long_task" "success" "Task complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Manual Status Updates**: Send one-off status messages
|
||||||
|
- **Automatic Monitoring**: Periodic "heartbeat" updates every 5 seconds
|
||||||
|
- **ASCII Fallback**: Uses ASCII symbols (->, OK, !, ?) on Windows CMD
|
||||||
|
- **Emoji Support**: Uses emojis (🔄, ✅, ❌, ⚠️) on Windows Terminal/PowerShell
|
||||||
|
- **Background Monitoring**: Runs independently until stopped
|
||||||
|
- **State Management**: Tracks active monitors in `.task_status_state.json`
|
||||||
|
|
||||||
|
## Status Types
|
||||||
|
|
||||||
|
| Type | Emoji | ASCII | Use Case |
|
||||||
|
|------|-------|-------|----------|
|
||||||
|
| progress | 🔄 | -> | Ongoing work |
|
||||||
|
| success | ✅ | OK | Completed successfully |
|
||||||
|
| error | ❌ | ! | Failed, cannot continue |
|
||||||
|
| warning | ⚠️ | ? | Issue but continuing |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Long File Processing
|
||||||
|
```bash
|
||||||
|
# Start monitor
|
||||||
|
python monitor_task.py start "video_convert" "progress"
|
||||||
|
|
||||||
|
# Convert video (takes 5 minutes)
|
||||||
|
ffmpeg -i input.mp4 output.mp4
|
||||||
|
|
||||||
|
# Stop monitor
|
||||||
|
python monitor_task.py stop "video_convert" "success" "Conversion complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Migration
|
||||||
|
```bash
|
||||||
|
# Start monitor with 10-second interval
|
||||||
|
python monitor_task.py start "db_migration" "progress" --interval 10
|
||||||
|
|
||||||
|
# Run migration
|
||||||
|
python migrate_db.py
|
||||||
|
|
||||||
|
# Stop monitor
|
||||||
|
python monitor_task.py stop "db_migration" "success" "Migrated 50,000 records"
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Rate Limiting
|
||||||
|
```bash
|
||||||
|
# Start monitor
|
||||||
|
python monitor_task.py start "api_sync" "progress"
|
||||||
|
|
||||||
|
# Make 1000 API calls (takes 10 minutes)
|
||||||
|
python sync_api.py
|
||||||
|
|
||||||
|
# Stop monitor
|
||||||
|
python monitor_task.py stop "api_sync" "success" "All calls successful"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring Commands
|
||||||
|
|
||||||
|
- `monitor_task.py start <name> <status> [--interval <seconds>]` - Start monitoring
|
||||||
|
- `monitor_task.py stop <name> <status> <message>` - Stop and send final status
|
||||||
|
- `monitor_task.py status` - View active monitors
|
||||||
|
- `monitor_task.py cancel_all` - Cancel all monitors (no final status)
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
task-status/
|
||||||
|
├── SKILL.md # Skill metadata and documentation
|
||||||
|
├── references/
|
||||||
|
│ └── usage.md # Detailed usage guide
|
||||||
|
├── scripts/
|
||||||
|
│ ├── send_status.py # Manual status updates
|
||||||
|
│ └── monitor_task.py # Automatic periodic monitoring
|
||||||
|
├── .task_status_state.json # Active monitor state (generated)
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with Clawdbot
|
||||||
|
|
||||||
|
Add to your workspace in `AGENTS.md` or `TOOLS.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Task Status
|
||||||
|
- Manual updates: `python scripts/send_status.py "message" "type" "step"`
|
||||||
|
- Auto monitoring: `python monitor_task.py start "task" "progress"`
|
||||||
|
- Periodic updates: Every 5 seconds automatically
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
1. **Short Messages**: Keep status messages under 140 characters
|
||||||
|
2. **Specific Names**: Use descriptive task names for clarity
|
||||||
|
3. **Always Stop**: Remember to stop the monitor with final status
|
||||||
|
4. **Check Status**: Use `monitor_task.py status` to see active monitors
|
||||||
|
5. **Cleanup**: Use `cancel_all` if monitors get stuck
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Monitor stuck**: Run `python monitor_task.py cancel_all`
|
||||||
|
- **No output**: Check if monitor is running with `status` command
|
||||||
|
- **Encoding issues**: ASCII fallback will be used automatically
|
||||||
|
- **Task done but monitor still running**: Stop it manually with `stop` command
|
||||||
147
SKILL.md
Normal file
147
SKILL.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
---
|
||||||
|
name: task-status
|
||||||
|
description: "为长时间运行的任务在聊天中发送简短状态描述。"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task Status Skill
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Manual Status Updates
|
||||||
|
```bash
|
||||||
|
python scripts/send_status.py "Starting data fetch..." "progress" "step1"
|
||||||
|
python scripts/send_status.py "Processing complete" "success" "final"
|
||||||
|
python scripts/send_status.py "Error: Missing API key" "error" "auth"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Periodic Monitoring (Every 5 seconds)
|
||||||
|
```bash
|
||||||
|
# Start monitoring a long-running task
|
||||||
|
python scripts/monitor_task.py start "My Long Task" "processing"
|
||||||
|
|
||||||
|
# Monitor will send "Still working..." updates every 5 seconds
|
||||||
|
# When task completes, report final status
|
||||||
|
python scripts/monitor_task.py stop "My Long Task" "success" "Completed successfully!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Status Types
|
||||||
|
|
||||||
|
- **progress**: Ongoing work (shows 🔄 or ->)
|
||||||
|
- **success**: Task complete (shows ✅ or OK)
|
||||||
|
- **error**: Failed task (shows ❌ or !)
|
||||||
|
- **warning**: Issue but continuing (shows ⚠️ or ?)
|
||||||
|
|
||||||
|
## Periodic Monitoring
|
||||||
|
|
||||||
|
The `monitor_task.py` script provides automatic updates:
|
||||||
|
|
||||||
|
### Starting Monitor
|
||||||
|
```bash
|
||||||
|
python scripts/monitor_task.py start "<task_name>" "<status_type>" [--interval <seconds>]
|
||||||
|
```
|
||||||
|
|
||||||
|
- Automatically sends "Still working..." updates every 5 seconds
|
||||||
|
- Runs in background until stopped
|
||||||
|
- Can be customized with different intervals
|
||||||
|
|
||||||
|
### Stopping Monitor
|
||||||
|
```bash
|
||||||
|
python scripts/monitor_task.py stop "<task_name>" "<final_status>" "<final_message>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Long File Processing
|
||||||
|
```bash
|
||||||
|
# Start monitoring
|
||||||
|
python scripts/monitor_task.py start "video_processing" "progress"
|
||||||
|
|
||||||
|
# ... long processing happens here ...
|
||||||
|
|
||||||
|
# Stop with final status
|
||||||
|
python scripts/monitor_task.py stop "video_processing" "success" "Processing complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Updates (Quick Status)
|
||||||
|
|
||||||
|
For single status updates without monitoring:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/send_status.py "Still fetching data..." "progress" "fetch"
|
||||||
|
python scripts/send_status.py "Processing records: 250/1000" "progress" "process"
|
||||||
|
python scripts/send_status.py "Complete! 3 files ready" "success" "final"
|
||||||
|
python scripts/send_status.py "Error: Connection timeout" "error" "api"
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use Each Method
|
||||||
|
|
||||||
|
### Use Manual Updates When:
|
||||||
|
- Task is short (under 30 seconds)
|
||||||
|
- You want control over when updates are sent
|
||||||
|
- Task has discrete, meaningful milestones
|
||||||
|
|
||||||
|
### Use Periodic Monitoring When:
|
||||||
|
- Task is long-running (over 1 minute)
|
||||||
|
- You want consistent "heartbeat" updates every 5 seconds
|
||||||
|
- Task has long periods of quiet work
|
||||||
|
- You want to reassure user that work is ongoing
|
||||||
|
|
||||||
|
## Message Guidelines
|
||||||
|
|
||||||
|
Keep status messages under 140 characters. Examples:
|
||||||
|
|
||||||
|
- **Progress**: "Still fetching data..." or "Processing records: 250/1000"
|
||||||
|
- **Success**: "Complete! 3 files ready" or "Task finished successfully"
|
||||||
|
- **Error**: "Error: Connection timeout" or "Failed: Missing API key"
|
||||||
|
- **Warning**: "Continuing despite timeout" or "Partial success: 5/10 files"
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### With Additional Details
|
||||||
|
```bash
|
||||||
|
python scripts/send_status.py "Uploading..." "progress" "upload" --details "File: report.pdf (2.4MB)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Different Intervals
|
||||||
|
```bash
|
||||||
|
python scripts/monitor_task.py start "data_sync" "progress" --interval 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Importing for Python Scripts
|
||||||
|
```python
|
||||||
|
from send_status import send_status
|
||||||
|
|
||||||
|
def long_task():
|
||||||
|
send_status("Starting...", "progress", "step1")
|
||||||
|
# ... work
|
||||||
|
send_status("Step complete", "success", "step1")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Automation with Clawdbot Cron
|
||||||
|
|
||||||
|
For scheduled tasks, use Clawdbot's cron feature:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# In a script or session
|
||||||
|
from cron import add
|
||||||
|
|
||||||
|
# Every 5 seconds, check status
|
||||||
|
job = {
|
||||||
|
"text": "Check status update",
|
||||||
|
"interval": "5s",
|
||||||
|
"enabled": True
|
||||||
|
}
|
||||||
|
add(job)
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows status updates even when you're not actively watching.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To use this skill, copy the `task-status` folder into your Clawdbot skills directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\Users\Luffy\AppData\Roaming\npm\node_modules\clawdbot\skills\task-status
|
||||||
|
```
|
||||||
|
|
||||||
|
Or add it to your workspace and reference it from `AGENTS.md` or `TOOLS.md`.
|
||||||
|
|
||||||
|
Once installed, the skill will be available for any task where you need periodic status updates.
|
||||||
6
_meta.json
Normal file
6
_meta.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"ownerId": "kn7e5frkfxx996cjqrkhryj95x7zx5we",
|
||||||
|
"slug": "task-status",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"publishedAt": 1769504488946
|
||||||
|
}
|
||||||
202
references/usage.md
Normal file
202
references/usage.md
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
# Task Status Usage Guide
|
||||||
|
|
||||||
|
## Quick Examples
|
||||||
|
|
||||||
|
### Manual Status Updates (Single Messages)
|
||||||
|
```bash
|
||||||
|
# Progress update
|
||||||
|
python send_status.py "Fetching data..." "progress" "fetch"
|
||||||
|
|
||||||
|
# Success update
|
||||||
|
python send_status.py "Done! 150 records processed" "success" "process"
|
||||||
|
|
||||||
|
# Error update
|
||||||
|
python send_status.py "Failed to connect to API" "error" "api_call"
|
||||||
|
|
||||||
|
# Warning update
|
||||||
|
python send_status.py "Continuing despite timeout" "warning" "timeout"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automated Periodic Monitoring (Every 5 seconds)
|
||||||
|
```bash
|
||||||
|
# Start monitoring a long task
|
||||||
|
python monitor_task.py start "video_processing" "progress"
|
||||||
|
|
||||||
|
# Monitor runs in background, sending "Still working..." updates every 5 seconds
|
||||||
|
|
||||||
|
# Stop monitoring with final status
|
||||||
|
python monitor_task.py stop "video_processing" "success" "Processing complete!"
|
||||||
|
|
||||||
|
# Or with an error
|
||||||
|
python monitor_task.py stop "video_processing" "error" "Failed: Corrupt file"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python Script
|
||||||
|
```python
|
||||||
|
from send_status import send_status
|
||||||
|
|
||||||
|
def process_data():
|
||||||
|
send_status("Reading files...", "progress", "read")
|
||||||
|
# ... work
|
||||||
|
send_status("Processing complete", "success", "process")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shell Script
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
python send_status.py "Starting backup..." "progress" "backup"
|
||||||
|
# ... backup command
|
||||||
|
python send_status.py "Backup complete" "success" "backup"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Status Types
|
||||||
|
|
||||||
|
| Type | Emoji | ASCII | When to Use |
|
||||||
|
|------|-------|-------|-------------|
|
||||||
|
| progress | 🔄 | -> | Ongoing work, "still working on it" |
|
||||||
|
| success | ✅ | OK | Task completed successfully |
|
||||||
|
| error | ❌ | ! | Task failed, cannot continue |
|
||||||
|
| warning | ⚠️ | ? | Issue but continuing |
|
||||||
|
|
||||||
|
## When to Use Each Method
|
||||||
|
|
||||||
|
### Use Manual Updates When:
|
||||||
|
- Task is short (under 30 seconds)
|
||||||
|
- You want control over when updates are sent
|
||||||
|
- Task has discrete, meaningful milestones
|
||||||
|
|
||||||
|
### Use Periodic Monitoring When:
|
||||||
|
- Task is long-running (over 1 minute)
|
||||||
|
- You want consistent "heartbeat" updates every 5 seconds
|
||||||
|
- Task has long periods of quiet work
|
||||||
|
- You want to reassure user that work is ongoing
|
||||||
|
|
||||||
|
## Periodic Monitoring Details
|
||||||
|
|
||||||
|
### Starting a Monitor
|
||||||
|
```bash
|
||||||
|
python monitor_task.py start "<task_name>" "<status_type>" [--interval <seconds>]
|
||||||
|
```
|
||||||
|
|
||||||
|
- Sends "Still working..." updates every 5 seconds by default
|
||||||
|
- Runs in background until stopped
|
||||||
|
- Can customize interval with `--interval`
|
||||||
|
|
||||||
|
### Viewing Active Monitors
|
||||||
|
```bash
|
||||||
|
python monitor_task.py status
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cancelling All Monitors (Without Final Status)
|
||||||
|
```bash
|
||||||
|
python monitor_task.py cancel_all
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Keep messages short** - Under 140 characters
|
||||||
|
2. **Be specific** - Include step names for clarity
|
||||||
|
3. **Update periodically** - Every ~4 seconds for long tasks (or use monitoring)
|
||||||
|
4. **Use details** - Add extra context when needed
|
||||||
|
5. **End with status** - Always send final success/error
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Multi-step Task (Manual)
|
||||||
|
```bash
|
||||||
|
python send_status.py "Step 1/5: Validating input" "progress" "step1"
|
||||||
|
# ... step 1
|
||||||
|
python send_status.py "Step 2/5: Processing data" "progress" "step2"
|
||||||
|
# ... step 2
|
||||||
|
# ... etc
|
||||||
|
python send_status.py "All steps complete" "success" "final"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Long-Running Task (Automatic Monitoring)
|
||||||
|
```bash
|
||||||
|
# Start monitor before starting the task
|
||||||
|
python monitor_task.py start "data_migration" "progress"
|
||||||
|
|
||||||
|
# Run the actual task (can take minutes/hours)
|
||||||
|
# Monitor sends "Still working..." updates every 5 seconds
|
||||||
|
|
||||||
|
# When task finishes, stop monitor with final status
|
||||||
|
python monitor_task.py stop "data_migration" "success" "Migration complete: 5000 records"
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Details
|
||||||
|
```bash
|
||||||
|
python send_status.py "Uploading..." "progress" "upload" --details "File: report.pdf (2.4MB)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Recovery
|
||||||
|
```bash
|
||||||
|
python send_status.py "Connection failed, retrying..." "warning" "retry"
|
||||||
|
# ... retry logic
|
||||||
|
if success:
|
||||||
|
python send_status.py "Retry successful" "success" "retry"
|
||||||
|
else:
|
||||||
|
python send_status.py "Retry failed, giving up" "error" "retry"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Long Task with Manual Control
|
||||||
|
```bash
|
||||||
|
# Start monitor
|
||||||
|
python monitor_task.py start "processing" "progress"
|
||||||
|
|
||||||
|
# ... do work ...
|
||||||
|
|
||||||
|
# Check status periodically
|
||||||
|
python monitor_task.py status
|
||||||
|
|
||||||
|
# When done, stop monitor
|
||||||
|
python monitor_task.py stop "processing" "success" "Finished!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
### Import send_status for Python Scripts
|
||||||
|
```python
|
||||||
|
from send_status import send_status
|
||||||
|
|
||||||
|
msg = send_status("Working...", "progress", "work")
|
||||||
|
print(f"Logged: {msg}") # Output: "-> [work] Working..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use in Shell Scripts
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
send_status() {
|
||||||
|
python send_status.py "$1" "$2" "$3"
|
||||||
|
}
|
||||||
|
|
||||||
|
send_status "Starting process" "progress" "main"
|
||||||
|
# ... process
|
||||||
|
send_status "Done" "success" "main"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automation with Clawdbot Cron
|
||||||
|
For scheduled tasks that need periodic status updates, use Clawdbot's cron feature.
|
||||||
|
|
||||||
|
## Monitoring Use Cases
|
||||||
|
|
||||||
|
### File Processing
|
||||||
|
```bash
|
||||||
|
python monitor_task.py start "file_proc" "progress"
|
||||||
|
# Process 1000 files (takes 10 minutes)
|
||||||
|
python monitor_task.py stop "file_proc" "success" "Processed 1000 files"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Sync
|
||||||
|
```bash
|
||||||
|
python monitor_task.py start "sync" "progress" --interval 10
|
||||||
|
# Sync databases (takes 5 minutes)
|
||||||
|
python monitor_task.py stop "sync" "success" "Sync complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Calls
|
||||||
|
```bash
|
||||||
|
python monitor_task.py start "api_call" "progress"
|
||||||
|
# Make 1000 API requests
|
||||||
|
python monitor_task.py stop "api_call" "success" "All 1000 requests successful"
|
||||||
|
```
|
||||||
273
scripts/monitor_task.py
Normal file
273
scripts/monitor_task.py
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Monitor a long-running task and send periodic status updates every 5 seconds.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
# Start monitoring
|
||||||
|
python monitor_task.py start "<task_name>" "<status_type>" [--interval <seconds>]
|
||||||
|
|
||||||
|
# Stop monitoring (sends final status)
|
||||||
|
python monitor_task.py stop "<task_name>" "<final_status>" "<final_message>"
|
||||||
|
|
||||||
|
Status Types (for final status):
|
||||||
|
progress - Ongoing work (shows 🔄)
|
||||||
|
success - Task complete (shows ✅)
|
||||||
|
error - Failed task (shows ❌)
|
||||||
|
warning - Issue but continuing (shows ⚠️)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
# Start monitoring a video processing task
|
||||||
|
python monitor_task.py start "video_proc" "progress"
|
||||||
|
|
||||||
|
# Later, stop with success
|
||||||
|
python monitor_task.py stop "video_proc" "success" "Processing complete!"
|
||||||
|
|
||||||
|
# Or with an error
|
||||||
|
python monitor_task.py stop "video_proc" "error" "Failed: Corrupt file"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
from send_status import send_status, can_encode_emoji
|
||||||
|
|
||||||
|
# State file to track active monitors
|
||||||
|
# Path: C:\Users\Luffy\clawd\task-status\scripts\monitor_task.py
|
||||||
|
# Parent: C:\Users\Luffy\clawd\task-status\scripts
|
||||||
|
# Parent.parent: C:\Users\Luffy\clawd\task-status
|
||||||
|
STATE_FILE = Path(__file__).parent.parent / ".task_status_state.json"
|
||||||
|
|
||||||
|
def load_state():
|
||||||
|
"""Load active monitors from state file."""
|
||||||
|
if STATE_FILE.exists():
|
||||||
|
try:
|
||||||
|
with open(STATE_FILE, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_state(state):
|
||||||
|
"""Save active monitors to state file."""
|
||||||
|
with open(STATE_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(state, f, indent=2)
|
||||||
|
|
||||||
|
def monitor_worker(task_name: str, interval: int = 5):
|
||||||
|
"""
|
||||||
|
Background worker that sends periodic updates.
|
||||||
|
Runs until stopped via state file check.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
print(f"[DEBUG] monitor_worker started for '{task_name}' with {interval}s interval", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
last_update = time.time()
|
||||||
|
print(f"[DEBUG] Initial last_update: {last_update}", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
iteration = 0
|
||||||
|
while True:
|
||||||
|
iteration += 1
|
||||||
|
|
||||||
|
# Check if task still exists in state
|
||||||
|
state = load_state()
|
||||||
|
print(f"[DEBUG] Iteration {iteration}: State keys = {list(state.keys())}", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
print(f"[DEBUG] Checking if '{task_name}' in state: {task_name in state}", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
if task_name not in state:
|
||||||
|
print(f"[DEBUG] Task '{task_name}' not in state, stopping worker", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
# Task was stopped
|
||||||
|
break
|
||||||
|
|
||||||
|
# Send "Still working..." update if enough time has passed
|
||||||
|
current_time = time.time()
|
||||||
|
elapsed = current_time - last_update
|
||||||
|
print(f"[DEBUG] Iteration {iteration}: elapsed={elapsed:.1f}s, interval={interval}s", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
if elapsed >= interval:
|
||||||
|
print(f"[DEBUG] Time to send update! elapsed={elapsed:.1f}s >= interval={interval}s", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
# Send progress update
|
||||||
|
message = f"Still working..."
|
||||||
|
status = "progress"
|
||||||
|
|
||||||
|
# Try to send status
|
||||||
|
try:
|
||||||
|
print(f"[DEBUG] Sending status: {message} {status} {task_name}", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
send_status(message, status, task_name)
|
||||||
|
print(f"[DEBUG] Status sent successfully", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
except Exception as e:
|
||||||
|
# If send fails, log but continue
|
||||||
|
print(f"[DEBUG] Monitor warning: {e}", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
last_update = current_time
|
||||||
|
print(f"[DEBUG] Updated last_update: {last_update}", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
else:
|
||||||
|
print(f"[DEBUG] Not time yet, sleeping...", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
# Sleep briefly to avoid tight loop (reduced from 0.5 to 0.1)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
print(f"[DEBUG] monitor_worker finished", file=sys.stderr)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
def start_monitor(task_name: str, status_type: str = "progress", interval: int = 5):
|
||||||
|
"""
|
||||||
|
Start a new monitor for the given task.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
print(f"[DEBUG] start_monitor called: task_name='{task_name}', status_type='{status_type}', interval={interval}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Validate status type
|
||||||
|
if status_type not in ["progress", "success", "error", "warning"]:
|
||||||
|
print(f"Invalid status type: {status_type}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if already monitoring
|
||||||
|
state = load_state()
|
||||||
|
print(f"[DEBUG] Current state: {state}", file=sys.stderr)
|
||||||
|
|
||||||
|
if task_name in state:
|
||||||
|
print(f"Already monitoring task '{task_name}'", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Add to state
|
||||||
|
state[task_name] = {
|
||||||
|
"status_type": status_type,
|
||||||
|
"interval": interval,
|
||||||
|
"started_at": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
print(f"[DEBUG] Saving new state: {state}", file=sys.stderr)
|
||||||
|
save_state(state)
|
||||||
|
|
||||||
|
# Send initial status
|
||||||
|
print(f"[DEBUG] Sending initial status", file=sys.stderr)
|
||||||
|
send_status(f"Monitoring started (updates every {interval}s)", status_type, task_name)
|
||||||
|
|
||||||
|
# Start background monitor thread
|
||||||
|
print(f"[DEBUG] Starting monitor thread", file=sys.stderr)
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=monitor_worker,
|
||||||
|
args=(task_name, interval),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
print(f"[DEBUG] Monitor thread started: {thread}", file=sys.stderr)
|
||||||
|
|
||||||
|
print(f"Monitor started for '{task_name}' with {interval}s interval")
|
||||||
|
|
||||||
|
def stop_monitor(task_name: str, final_status: str, final_message: str):
|
||||||
|
"""
|
||||||
|
Stop monitoring and send final status.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
print(f"[DEBUG] stop_monitor called: task_name='{task_name}', final_status='{final_status}', final_message='{final_message}'", file=sys.stderr)
|
||||||
|
|
||||||
|
# Check if monitoring
|
||||||
|
state = load_state()
|
||||||
|
print(f"[DEBUG] Current state: {state}", file=sys.stderr)
|
||||||
|
|
||||||
|
if task_name not in state:
|
||||||
|
print(f"No active monitor for task '{task_name}'", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Remove from state
|
||||||
|
print(f"[DEBUG] Removing '{task_name}' from state", file=sys.stderr)
|
||||||
|
del state[task_name]
|
||||||
|
print(f"[DEBUG] New state: {state}", file=sys.stderr)
|
||||||
|
save_state(state)
|
||||||
|
|
||||||
|
# Send final status
|
||||||
|
print(f"[DEBUG] Sending final status", file=sys.stderr)
|
||||||
|
send_status(final_message, final_status, task_name)
|
||||||
|
|
||||||
|
print(f"Monitor stopped for '{task_name}'")
|
||||||
|
print(f"Final status: {final_message}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print(__doc__)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
action = sys.argv[1].lower()
|
||||||
|
|
||||||
|
if action == "start":
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print("Usage: monitor_task.py start <task_name> <status_type> [--interval <seconds>]", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
task_name = sys.argv[2]
|
||||||
|
status_type = sys.argv[3]
|
||||||
|
interval = 5
|
||||||
|
|
||||||
|
# Check for --interval flag
|
||||||
|
if len(sys.argv) > 5 and sys.argv[4] == "--interval":
|
||||||
|
try:
|
||||||
|
interval = int(sys.argv[5])
|
||||||
|
except ValueError:
|
||||||
|
print("Interval must be an integer", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
start_monitor(task_name, status_type, interval)
|
||||||
|
|
||||||
|
elif action == "stop":
|
||||||
|
if len(sys.argv) < 5:
|
||||||
|
print("Usage: monitor_task.py stop <task_name> <final_status> <final_message>", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
task_name = sys.argv[2]
|
||||||
|
final_status = sys.argv[3]
|
||||||
|
final_message = sys.argv[4]
|
||||||
|
|
||||||
|
stop_monitor(task_name, final_status, final_message)
|
||||||
|
|
||||||
|
elif action == "status":
|
||||||
|
# Show active monitors
|
||||||
|
state = load_state()
|
||||||
|
if not state:
|
||||||
|
print("No active monitors")
|
||||||
|
else:
|
||||||
|
print("Active monitors:")
|
||||||
|
for task, info in state.items():
|
||||||
|
print(f" {task}: {info['status_type']} (interval: {info['interval']}s)")
|
||||||
|
|
||||||
|
elif action == "cancel_all":
|
||||||
|
# Stop all monitors without sending final status
|
||||||
|
state = load_state()
|
||||||
|
if state:
|
||||||
|
for task_name in list(state.keys()):
|
||||||
|
del state[task_name]
|
||||||
|
save_state(state)
|
||||||
|
print(f"Cancelled all monitors: {list(state.keys())}")
|
||||||
|
else:
|
||||||
|
print("No active monitors to cancel")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"Unknown action: {action}", file=sys.stderr)
|
||||||
|
print(__doc__)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
180
scripts/send_status.py
Normal file
180
scripts/send_status.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Send status messages for long-running tasks.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python send_status.py "<message>" "<status_type>" "<step_name>" [--details "<details>"]
|
||||||
|
|
||||||
|
Status Types:
|
||||||
|
progress - Ongoing work (shows 🔄)
|
||||||
|
success - Task complete (shows ✅)
|
||||||
|
error - Failed task (shows ❌)
|
||||||
|
warning - Issue but continuing (shows ⚠️)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
python send_status.py "Fetching data..." "progress" "fetch"
|
||||||
|
python send_status.py "Complete!" "success" "final"
|
||||||
|
python send_status.py "Error: Missing file" "error" "file_check"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import websocket
|
||||||
|
|
||||||
|
# Status type to emoji mapping
|
||||||
|
STATUS_EMOJIS = {
|
||||||
|
"progress": "🔄",
|
||||||
|
"success": "✅",
|
||||||
|
"error": "❌",
|
||||||
|
"warning": "⚠️"
|
||||||
|
}
|
||||||
|
|
||||||
|
def can_encode_emoji(text: str, encoding: str = None) -> bool:
|
||||||
|
"""Check if text can be encoded with the given encoding."""
|
||||||
|
if encoding is None:
|
||||||
|
encoding = sys.stdout.encoding
|
||||||
|
try:
|
||||||
|
text.encode(encoding)
|
||||||
|
return True
|
||||||
|
except (UnicodeEncodeError, LookupError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_status(message: str, status_type: str, step_name: str, details: str = None):
|
||||||
|
"""
|
||||||
|
Format and send a status message to Telegram.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Short status message (< 140 chars)
|
||||||
|
status_type: Type of status (progress, success, error, warning)
|
||||||
|
step_name: Name of the step being reported
|
||||||
|
details: Optional additional context
|
||||||
|
"""
|
||||||
|
if status_type not in STATUS_EMOJIS:
|
||||||
|
raise ValueError(f"Invalid status_type: {status_type}")
|
||||||
|
|
||||||
|
emoji = STATUS_EMOJIS[status_type]
|
||||||
|
|
||||||
|
# Choose emoji or ASCII based on encoding capability
|
||||||
|
if can_encode_emoji(emoji):
|
||||||
|
prefix = emoji
|
||||||
|
else:
|
||||||
|
prefix = emoji # Use emoji anyway, most modern terminals support it
|
||||||
|
|
||||||
|
# Build the message
|
||||||
|
formatted = f"{prefix} [{step_name}] {message}"
|
||||||
|
|
||||||
|
if details:
|
||||||
|
formatted += f" ({details})"
|
||||||
|
|
||||||
|
# Keep it concise (under 140 chars)
|
||||||
|
if len(formatted) > 140:
|
||||||
|
formatted = formatted[:137] + "..."
|
||||||
|
|
||||||
|
# Try WebSocket first (fastest)
|
||||||
|
gateway_token = os.environ.get("CLAWDBOT_GATEWAY_TOKEN")
|
||||||
|
|
||||||
|
if gateway_token:
|
||||||
|
try:
|
||||||
|
gateway_port = os.environ.get("CLAWDBOT_GATEWAY_PORT", "18789")
|
||||||
|
target = os.environ.get("TELEGRAM_TARGET", "7590912486")
|
||||||
|
ws_url = f"ws://127.0.0.1:{gateway_port}/ws"
|
||||||
|
|
||||||
|
# Connect and send
|
||||||
|
ws = websocket.create_connection(ws_url, timeout=10)
|
||||||
|
|
||||||
|
# Send message directly (no handshake needed for simple messages)
|
||||||
|
msg = {
|
||||||
|
"type": "message",
|
||||||
|
"action": "send",
|
||||||
|
"target": target,
|
||||||
|
"message": formatted,
|
||||||
|
"channel": "telegram"
|
||||||
|
}
|
||||||
|
ws.send(json.dumps(msg))
|
||||||
|
|
||||||
|
# Try to receive response but don't wait too long
|
||||||
|
try:
|
||||||
|
response = ws.recv()
|
||||||
|
result = json.loads(response)
|
||||||
|
# If we got a challenge, respond to it
|
||||||
|
if result.get("event") == "connect.challenge":
|
||||||
|
# Send handshake with token
|
||||||
|
handshake = {"type": "handshake", "token": gateway_token}
|
||||||
|
ws.send(json.dumps(handshake))
|
||||||
|
# Try to receive again
|
||||||
|
response = ws.recv()
|
||||||
|
result = json.loads(response)
|
||||||
|
except:
|
||||||
|
# If we can't receive, assume message was sent
|
||||||
|
pass
|
||||||
|
|
||||||
|
ws.close()
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ WebSocket failed: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Fallback: try CLI
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
clawdbot_path = shutil.which("clawdbot")
|
||||||
|
|
||||||
|
if not clawdbot_path:
|
||||||
|
clawdbot_paths = [
|
||||||
|
"C:\\Users\\Luffy\\AppData\\Roaming\\npm\\clawdbot.cmd",
|
||||||
|
"C:\\Users\\Luffy\\AppData\\Roaming\\npm\\clawdbot"
|
||||||
|
]
|
||||||
|
for path in clawdbot_paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
clawdbot_path = path
|
||||||
|
break
|
||||||
|
|
||||||
|
if clawdbot_path:
|
||||||
|
try:
|
||||||
|
target = os.environ.get("TELEGRAM_TARGET", "7590912486")
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
clawdbot_path,
|
||||||
|
"message",
|
||||||
|
"send",
|
||||||
|
"--target", target,
|
||||||
|
"--message", formatted,
|
||||||
|
"--channel", "telegram"
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=20
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return formatted
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ CLI failed: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Final fallback: print to console
|
||||||
|
print(formatted, file=sys.stderr)
|
||||||
|
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print(__doc__)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
message = sys.argv[1]
|
||||||
|
status_type = sys.argv[2]
|
||||||
|
step_name = sys.argv[3]
|
||||||
|
details = sys.argv[4] if len(sys.argv) > 4 else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = send_status(message, status_type, step_name, details)
|
||||||
|
print(f"Status sent: {result}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending status: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
113
scripts/send_status_websocket.py
Normal file
113
scripts/send_status_websocket.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Send status messages using Clawdbot WebSocket API (fast).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import websocket
|
||||||
|
|
||||||
|
# Status type to emoji mapping
|
||||||
|
STATUS_EMOJIS = {
|
||||||
|
"progress": "🔄",
|
||||||
|
"success": "✅",
|
||||||
|
"error": "❌",
|
||||||
|
"warning": "⚠️"
|
||||||
|
}
|
||||||
|
|
||||||
|
def send_status(message: str, status_type: str, step_name: str, details: str = None):
|
||||||
|
"""
|
||||||
|
Format and send a status message to Telegram via WebSocket.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Short status message (< 140 chars)
|
||||||
|
status_type: Type of status (progress, success, error, warning)
|
||||||
|
step_name: Name of the step being reported
|
||||||
|
details: Optional additional context
|
||||||
|
"""
|
||||||
|
if status_type not in STATUS_EMOJIS:
|
||||||
|
raise ValueError(f"Invalid status_type: {status_type}")
|
||||||
|
|
||||||
|
emoji = STATUS_EMOJIS[status_type]
|
||||||
|
|
||||||
|
# Build the message
|
||||||
|
formatted = f"{emoji} [{step_name}] {message}"
|
||||||
|
|
||||||
|
if details:
|
||||||
|
formatted += f" ({details})"
|
||||||
|
|
||||||
|
# Keep it concise (under 140 chars)
|
||||||
|
if len(formatted) > 140:
|
||||||
|
formatted = formatted[:137] + "..."
|
||||||
|
|
||||||
|
# Send via WebSocket
|
||||||
|
gateway_port = os.environ.get("CLAWDBOT_GATEWAY_PORT", "18789")
|
||||||
|
gateway_token = os.environ.get("CLAWDBOT_GATEWAY_TOKEN")
|
||||||
|
target = os.environ.get("TELEGRAM_TARGET", "7590912486")
|
||||||
|
|
||||||
|
if not gateway_token:
|
||||||
|
print(f"✗ CLAWDBOT_GATEWAY_TOKEN not found", file=sys.stderr)
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
ws_url = f"ws://127.0.0.1:{gateway_port}/ws"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Connect and send
|
||||||
|
ws = websocket.create_connection(ws_url, timeout=10)
|
||||||
|
|
||||||
|
# Handshake
|
||||||
|
handshake = {"type": "handshake", "token": gateway_token}
|
||||||
|
ws.send(json.dumps(handshake))
|
||||||
|
|
||||||
|
# Send message
|
||||||
|
msg = {
|
||||||
|
"type": "message",
|
||||||
|
"action": "send",
|
||||||
|
"target": target,
|
||||||
|
"message": formatted,
|
||||||
|
"channel": "telegram"
|
||||||
|
}
|
||||||
|
ws.send(json.dumps(msg))
|
||||||
|
|
||||||
|
# Get response
|
||||||
|
response = ws.recv()
|
||||||
|
result = json.loads(response)
|
||||||
|
|
||||||
|
ws.close()
|
||||||
|
|
||||||
|
# Check if message was sent
|
||||||
|
if result.get("event") == "message.sent" or result.get("type") == "ack":
|
||||||
|
return formatted
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Response: {result}", file=sys.stderr)
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ WebSocket error: {e}", file=sys.stderr)
|
||||||
|
# Fallback: print to console
|
||||||
|
print(formatted, file=sys.stderr)
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print("Usage: send_status_websocket.py <message> <status_type> <step_name>")
|
||||||
|
print("\nStatus Types: progress, success, error, warning")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
message = sys.argv[1]
|
||||||
|
status_type = sys.argv[2]
|
||||||
|
step_name = sys.argv[3]
|
||||||
|
details = sys.argv[4] if len(sys.argv) > 4 else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = send_status(message, status_type, step_name, details)
|
||||||
|
print(f"✓ Status sent: {result}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
215
scripts/send_status_with_logging.py
Normal file
215
scripts/send_status_with_logging.py
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Send status messages to Telegram with automatic logging.
|
||||||
|
This is the production version of send_status.py.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import websocket
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
LOG_DIR = Path("C:/Users/Luffy/clawd/logs")
|
||||||
|
LOG_FILE = LOG_DIR / "telegram_messages.jsonl"
|
||||||
|
TASK_LOG_FILE = LOG_DIR / "task_status.jsonl"
|
||||||
|
|
||||||
|
# Status type to emoji mapping
|
||||||
|
STATUS_EMOJIS = {
|
||||||
|
"progress": "🔄",
|
||||||
|
"success": "✅",
|
||||||
|
"error": "❌",
|
||||||
|
"warning": "⚠️"
|
||||||
|
}
|
||||||
|
|
||||||
|
def ensure_log_dir():
|
||||||
|
"""Create log directory if it doesn't exist."""
|
||||||
|
LOG_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
def log_message(message: str, direction: str = "out", task_name: str = None, status_type: str = None):
|
||||||
|
"""Log a message to the file."""
|
||||||
|
ensure_log_dir()
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = {
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
"direction": direction,
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
|
||||||
|
if task_name:
|
||||||
|
data["task"] = task_name
|
||||||
|
if status_type:
|
||||||
|
data["status"] = status_type
|
||||||
|
|
||||||
|
with open(LOG_FILE, 'a', encoding='utf-8') as f:
|
||||||
|
f.write(json.dumps(data, ensure_ascii=False) + '\n')
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[LOG] Error: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_encode_emoji(text: str, encoding: str = None) -> bool:
|
||||||
|
"""Check if text can be encoded with the given encoding."""
|
||||||
|
if encoding is None:
|
||||||
|
encoding = sys.stdout.encoding
|
||||||
|
try:
|
||||||
|
text.encode(encoding)
|
||||||
|
return True
|
||||||
|
except (UnicodeEncodeError, LookupError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_status(message: str, status_type: str, step_name: str, details: str = None):
|
||||||
|
"""
|
||||||
|
Format and send a status message to Telegram with logging.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Short status message (< 140 chars)
|
||||||
|
status_type: Type of status (progress, success, error, warning)
|
||||||
|
step_name: Name of the step being reported
|
||||||
|
details: Optional additional context
|
||||||
|
"""
|
||||||
|
if status_type not in STATUS_EMOJIS:
|
||||||
|
raise ValueError(f"Invalid status_type: {status_type}")
|
||||||
|
|
||||||
|
emoji = STATUS_EMOJIS[status_type]
|
||||||
|
|
||||||
|
# Choose emoji or ASCII based on encoding capability
|
||||||
|
if can_encode_emoji(emoji):
|
||||||
|
prefix = emoji
|
||||||
|
else:
|
||||||
|
prefix = emoji # Most modern terminals support emojis
|
||||||
|
|
||||||
|
# Build the message
|
||||||
|
formatted = f"{prefix} [{step_name}] {message}"
|
||||||
|
|
||||||
|
if details:
|
||||||
|
formatted += f" ({details})"
|
||||||
|
|
||||||
|
# Keep it concise (under 140 chars)
|
||||||
|
if len(formatted) > 140:
|
||||||
|
formatted = formatted[:137] + "..."
|
||||||
|
|
||||||
|
# Log the message (before sending)
|
||||||
|
log_message(formatted, direction="out", task_name=step_name, status_type=status_type)
|
||||||
|
|
||||||
|
# Try WebSocket first (fastest)
|
||||||
|
gateway_token = os.environ.get("CLAWDBOT_GATEWAY_TOKEN")
|
||||||
|
|
||||||
|
if gateway_token:
|
||||||
|
try:
|
||||||
|
gateway_port = os.environ.get("CLAWDBOT_GATEWAY_PORT", "18789")
|
||||||
|
target = os.environ.get("TELEGRAM_TARGET", "7590912486")
|
||||||
|
ws_url = f"ws://127.0.0.1:{gateway_port}/ws"
|
||||||
|
|
||||||
|
# Connect and send
|
||||||
|
ws = websocket.create_connection(ws_url, timeout=10)
|
||||||
|
|
||||||
|
# Send message directly (no handshake needed for simple messages)
|
||||||
|
msg = {
|
||||||
|
"type": "message",
|
||||||
|
"action": "send",
|
||||||
|
"target": target,
|
||||||
|
"message": formatted,
|
||||||
|
"channel": "telegram"
|
||||||
|
}
|
||||||
|
ws.send(json.dumps(msg))
|
||||||
|
|
||||||
|
# Try to receive response but don't wait too long
|
||||||
|
try:
|
||||||
|
response = ws.recv()
|
||||||
|
result = json.loads(response)
|
||||||
|
# If we got a challenge, respond to it
|
||||||
|
if result.get("event") == "connect.challenge":
|
||||||
|
# Send handshake with token
|
||||||
|
handshake = {
|
||||||
|
"type": "handshake",
|
||||||
|
"token": gateway_token,
|
||||||
|
"nonce": result.get("payload", {}).get("nonce")
|
||||||
|
}
|
||||||
|
ws.send(json.dumps(handshake))
|
||||||
|
# Try to receive again
|
||||||
|
response = ws.recv()
|
||||||
|
result = json.loads(response)
|
||||||
|
except:
|
||||||
|
# If we can't receive, assume message was sent
|
||||||
|
pass
|
||||||
|
|
||||||
|
ws.close()
|
||||||
|
|
||||||
|
# Log successful send
|
||||||
|
log_message(formatted, direction="out_sent", task_name=step_name, status_type=status_type)
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[LOG] WebSocket failed: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Fallback: try CLI
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
clawdbot_path = shutil.which("clawdbot")
|
||||||
|
|
||||||
|
if not clawdbot_path:
|
||||||
|
clawdbot_paths = [
|
||||||
|
"C:\\Users\\Luffy\\AppData\\Roaming\\npm\\clawdbot.cmd",
|
||||||
|
"C:\\Users\\Luffy\\AppData\\Roaming\\npm\\clawdbot"
|
||||||
|
]
|
||||||
|
for path in clawdbot_paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
clawdbot_path = path
|
||||||
|
break
|
||||||
|
|
||||||
|
if clawdbot_path:
|
||||||
|
try:
|
||||||
|
target = os.environ.get("TELEGRAM_TARGET", "7590912486")
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
clawdbot_path,
|
||||||
|
"message",
|
||||||
|
"send",
|
||||||
|
"--target", target,
|
||||||
|
"--message", formatted,
|
||||||
|
"--channel", "telegram"
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=20
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
# Log successful send
|
||||||
|
log_message(formatted, direction="out_sent", task_name=step_name, status_type=status_type)
|
||||||
|
return formatted
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[LOG] CLI failed: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Final fallback: print to console
|
||||||
|
print(formatted, file=sys.stderr)
|
||||||
|
log_message(formatted, direction="out_failed", task_name=step_name, status_type=status_type)
|
||||||
|
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print("Usage: send_status_with_logging.py <message> <status_type> <step_name>")
|
||||||
|
print("\nStatus Types: progress, success, error, warning")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
message = sys.argv[1]
|
||||||
|
status_type = sys.argv[2]
|
||||||
|
step_name = sys.argv[3]
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = send_status(message, status_type, step_name)
|
||||||
|
print(f"✓ Sent and logged: {result}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
62
scripts/test_send_status.py
Normal file
62
scripts/test_send_status.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script for send_status.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def test_send_status():
|
||||||
|
"""Test the status sending functionality."""
|
||||||
|
script_path = Path(__file__).parent / "send_status.py"
|
||||||
|
|
||||||
|
# Test cases
|
||||||
|
test_cases = [
|
||||||
|
("Test progress message", "progress", "test_step", None),
|
||||||
|
("Test success message", "success", "test_step", None),
|
||||||
|
("Test error message", "error", "test_step", None),
|
||||||
|
("Test warning message", "warning", "test_step", None),
|
||||||
|
("Long message test", "progress", "step1", "with details about the step"),
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Testing send_status.py...")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
for message, status_type, step_name, details in test_cases:
|
||||||
|
print(f"\nTest: {message}")
|
||||||
|
print(f" Type: {status_type}, Step: {step_name}")
|
||||||
|
|
||||||
|
# Build command
|
||||||
|
cmd = [sys.executable, str(script_path), message, status_type, step_name]
|
||||||
|
if details:
|
||||||
|
cmd.append(details)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run the command
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f" PASS: Success")
|
||||||
|
output = result.stdout.strip()
|
||||||
|
if output:
|
||||||
|
print(f" Output: {output}")
|
||||||
|
else:
|
||||||
|
print(f" FAIL: Failed with return code {result.returncode}")
|
||||||
|
if result.stderr:
|
||||||
|
print(f" Error: {result.stderr.strip()}")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
print(f" FAIL: Timed out")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" FAIL: Exception: {e}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("Testing complete")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_send_status()
|
||||||
Reference in New Issue
Block a user