feat(phase3): add notification system and kugetsu notify command

Phase 3c implementation - Notification System:

### New kugetsu commands:
- `kugetsu notify list` - Show unread notifications from PM Agent
- `kugetsu notify clear` - Mark notifications as read

### Notification system:
- PM Agent writes task events to ~/.kugetsu/notifications.json
- Events: task_complete, task_blocked, task_assigned
- Supports issue_ref and gitea_url for linking
- Hermes/Chat Agent reads notifications on user messages

### kugetsu-pm v2.0:
- Updated documentation with notification behavior
- PM Agent monitors Gitea for task completion
- Two review modes: PM reviews immediately OR asks dev if ready
- Notification triggers documented

### File renamed:
- phase3a-setup.md → kugetsu-chat-setup.md (more descriptive)

### Hermes gateway analysis:
- Gateway is a client (connects to Telegram), not a server
- Cannot push messages directly to Telegram from external process
- Notifications stored locally for Hermes to pick up on next user message
This commit is contained in:
shokollm
2026-03-31 02:19:50 +00:00
parent b3171ed632
commit 3d00ddbc1b
3 changed files with 255 additions and 91 deletions

View File

@@ -1,11 +1,11 @@
--- ---
name: kugetsu-pm name: kugetsu-pm
description: PM (Project Manager) Agent skill for kugetsu. Handles task coordination, delegation, and Gitea integration. description: PM (Project Manager) Agent skill for kugetsu. Handles task coordination, delegation, and notifications.
license: MIT license: MIT
compatibility: Requires kugetsu CLI, opencode sessions, Gitea API access. compatibility: Requires kugetsu CLI, opencode sessions, Gitea API access.
metadata: metadata:
author: shoko author: shoko
version: "1.0" version: "2.0"
--- ---
# kugetsu-pm - PM Agent Skill # kugetsu-pm - PM Agent Skill
@@ -18,37 +18,44 @@ The PM Agent is a persistent opencode session managed by kugetsu. It:
1. **Receives** task requests from Chat Agent (via Hermes) 1. **Receives** task requests from Chat Agent (via Hermes)
2. **Coordinates** task execution via Dev Agents 2. **Coordinates** task execution via Dev Agents
3. **Monitors** Gitea for issue updates 3. **Monitors** Gitea for issue/PR updates
4. **Notifies** users of task completion (if in notify mode) 4. **Notifies** users of task completion and status changes
5. **Maintains** context across interactions 5. **Maintains** context across interactions
## Architecture ## Architecture
``` ```
Chat Agent (Hermes/Telegram) User (Telegram) → Hermes → Chat Agent → PM Agent
├── Routes task requests ├── kugetsu start → Dev Agent
│ └── Work on issue
└── notify ← Gitea (optional)
PM Agent (opencode session via kugetsu) └── ~/.kugetsu/notifications.json
├── Creates Dev Agent sessions via kugetsu
Dev Agents (opencode sessions via kugetsu)
├── Work on issues autonomously
Gitea (Issues, PRs, Comments)
``` ```
## PM Agent Modes ## Notification System
| Mode | Behavior | Command | PM Agent notifies users via two channels:
|------|----------|---------|
| **notify** (default) | Send completion notifications | "pm notify" | ### 1. Local Notifications (Default)
| **silent** | Work quietly, no notifications | "pm silent" | - PM Agent writes to `~/.kugetsu/notifications.json`
- Hermes/Chat Agent reads this file when user sends a message
- User can view with `kugetsu notify list`
### 2. Gitea Comments (When Available)
- If task is issue/PR-related, PM Agent posts to Gitea
- User receives notification via Gitea's native notification system
- If Gitea is unavailable, PM Agent logs to notifications.json with a note
### Notification Triggers
| Event | Action |
|-------|--------|
| Task assigned to Dev Agent | Write to notifications.json |
| Task completed (PR merged/closed) | Write to notifications.json + Gitea |
| Task blocked | Write to notifications.json |
| Dev agent needs review | Two options: review immediately OR ask dev if ready |
## Task Flow ## Task Flow
@@ -79,77 +86,73 @@ PM Agent decides:
kugetsu start <issue-ref> "<task description>" kugetsu start <issue-ref> "<task description>"
``` ```
### 5. Monitor and Notify ### 5. Monitor for Completion
- PM monitors Gitea for PR status PM Agent monitors Gitea for task completion by checking:
- When complete, notifies user (if in notify mode) - Issue comments
- PR commits
- PR status (merged/open)
## Gitea Integration **Query pattern for completion:**
```bash
### Context Fetching # Check if issue/PR has recent activity
curl -s "https://git.fbrns.co/api/v1/repos/{owner}/{repo}/issues/{number}/comments"
PM Agent fetches from Gitea when: # Check for commits in PR branch
- Initial task load (no context)
- Explicit request (agent decides)
- Insufficient context
### Context Merge Strategy
- **Default**: Append new context to existing
- **Threshold**: Summarize + replace at 40% of context window
## Session Context
PM Agent maintains:
### Managed Repositories
```json
{
"repos": [
"github.com/shoko/kugetsu",
"gitlab.com/team/core"
]
}
``` ```
### Active Tasks ### 6. Review Decision
```json
{
"tasks": {
"issue-5": {
"status": "in_progress",
"dev_agent": "ses_xyz789",
"created_at": "2026-03-30T10:00:00Z"
}
}
}
```
### Notification Preferences When dev agent signals completion, PM Agent chooses:
**Option A: Review immediately**
- PM Agent reviews the PR/changes
- If good, merges or approves
- If issues, posts review comments
**Option B: Ask dev if ready**
- PM Agent posts comment: "Dev work complete. Please review and confirm if ready for merge."
- Waits for dev agent confirmation
- Then proceeds with review
### 7. Notify on Completion
After task completion:
1. Write to `~/.kugetsu/notifications.json`
2. Post to Gitea issue/PR comment (if available)
3. If Gitea fails, note in notifications.json
**Notification format:**
```json ```json
{ {
"mode": "notify" "type": "task_complete",
"message": "Issue #5 fixed. PR #12 created and merged.",
"issue_ref": "github.com/shoko/kugetsu#5",
"gitea_url": "https://git.fbrns.co/shoko/kugetsu/pulls/12",
"timestamp": "2026-03-31T10:00:00Z"
} }
``` ```
## Delegation Commands ## Delegation Commands
### Create Dev Agent Session ### Send message to PM Agent
```bash
kugetsu delegate "<message>"
```
### Create Dev Agent Session
```bash ```bash
kugetsu start <issue-ref> "<task>" kugetsu start <issue-ref> "<task>"
``` ```
### Continue Dev Agent Session ### Continue Dev Agent Session
```bash ```bash
kugetsu continue <issue-ref> "<update>" kugetsu continue <issue-ref> "<update>"
``` ```
### List Active Sessions ### Check Notifications
```bash ```bash
kugetsu list kugetsu notify list
kugetsu notify clear
``` ```
## Response Format ## Response Format
@@ -165,6 +168,7 @@ PM Agent responses should be:
"Created task for issue #5. Dev agent started." "Created task for issue #5. Dev agent started."
"Issue #5 is complete. PR created: [link]" "Issue #5 is complete. PR created: [link]"
"Task blocked: Need clarification on requirements." "Task blocked: Need clarification on requirements."
"Dev work ready for review. Merging PR #12."
``` ```
## Error Handling ## Error Handling
@@ -172,7 +176,7 @@ PM Agent responses should be:
### Dev Agent Failure ### Dev Agent Failure
- Analyze failure reason - Analyze failure reason
- Retry or escalate to user - Retry or escalate to user
- Log to Gitea issue comment - Log to notifications.json
### Session Not Found ### Session Not Found
- Check kugetsu status: `kugetsu list` - Check kugetsu status: `kugetsu list`
@@ -182,18 +186,7 @@ PM Agent responses should be:
### Gitea API Errors ### Gitea API Errors
- Retry with backoff - Retry with backoff
- Cache last known state - Cache last known state
- Inform user if persistent - Log to notifications.json (user will be notified there)
## Skills
### kugetsu (for session management)
- Session creation and continuation
- Worktree management
### github (for Gitea API)
- Issue fetching
- PR creation
- Comment posting
## Implementation Notes ## Implementation Notes
@@ -204,15 +197,36 @@ The PM Agent session is stored in:
~/.kugetsu/index.json → "pm_agent" field ~/.kugetsu/index.json → "pm_agent" field
``` ```
### Accessing PM Agent ### PM Context File (Optional)
```bash Customize PM Agent behavior by creating:
PM_SESSION=$(cat ~/.kugetsu/index.json | python3 -c "import sys,json; print(json.load(sys.stdin).get('pm_agent', ''))") ```
opencode run --continue --session "$PM_SESSION" "<message>" ~/.kugetsu/pm-agent.md
```
This file is injected into the PM Agent session at init time.
### Notifications File
Location: `~/.kugetsu/notifications.json`
Format:
```json
[
{
"type": "task_complete|task_blocked|task_assigned",
"message": "Human-readable message",
"issue_ref": "github.com/user/repo#5",
"gitea_url": "https://git.fbrns.co/user/repo/pulls/5",
"timestamp": "ISO8601",
"read": false
}
]
``` ```
## Related Documentation ## Related Documentation
- [kugetsu-architecture.md](../../docs/kugetsu-architecture.md) - [kugetsu-architecture.md](../../docs/kugetsu-architecture.md)
- [kugetsu-chat.md](../../docs/kugetsu-chat.md) - [kugetsu-chat.md](../../docs/kugetsu-chat.md)
- [kugetsu-chat-setup.md](../../docs/kugetsu-chat-setup.md)
- [hermes-setup.md](../../docs/hermes-setup.md) - [hermes-setup.md](../../docs/hermes-setup.md)

View File

@@ -6,6 +6,7 @@ SESSIONS_DIR="$KUGETSU_DIR/sessions"
WORKTREES_DIR="$KUGETSU_DIR/worktrees" WORKTREES_DIR="$KUGETSU_DIR/worktrees"
REPOS_CONFIG="$KUGETSU_DIR/repos.json" REPOS_CONFIG="$KUGETSU_DIR/repos.json"
INDEX_FILE="$KUGETSU_DIR/index.json" INDEX_FILE="$KUGETSU_DIR/index.json"
NOTIFICATIONS_FILE="$KUGETSU_DIR/notifications.json"
usage() { usage() {
cat << 'EOF' cat << 'EOF'
@@ -18,6 +19,7 @@ Usage:
kugetsu delegate <message> Send message to PM agent kugetsu delegate <message> Send message to PM agent
kugetsu status Check kugetsu initialization status kugetsu status Check kugetsu initialization status
kugetsu doctor [--fix] Diagnose and fix kugetsu issues kugetsu doctor [--fix] Diagnose and fix kugetsu issues
kugetsu notify [list|clear] Show or clear notifications
kugetsu list List all tracked sessions kugetsu list List all tracked sessions
kugetsu prune [--force] Remove orphaned sessions (keeps base + pm-agent) kugetsu prune [--force] Remove orphaned sessions (keeps base + pm-agent)
kugetsu destroy <issue-ref> [-y] Delete session for issue kugetsu destroy <issue-ref> [-y] Delete session for issue
@@ -39,6 +41,8 @@ Commands:
PM context is loaded once at init time. PM context is loaded once at init time.
status Check if kugetsu is initialized and PM agent is active. status Check if kugetsu is initialized and PM agent is active.
doctor Diagnose kugetsu issues. Use --fix to attempt repairs. doctor Diagnose kugetsu issues. Use --fix to attempt repairs.
notify Show or clear notifications from PM agent.
Use 'kugetsu notify list' to see unread notifications.
list Show all sessions (base + pm-agent + forked issues). list Show all sessions (base + pm-agent + forked issues).
prune Remove sessions not in index (orphaned from opencode). prune Remove sessions not in index (orphaned from opencode).
Use --force to skip confirmation. Use --force to skip confirmation.
@@ -52,12 +56,18 @@ PM Context:
into the PM agent session at init time. This allows customizing PM into the PM agent session at init time. This allows customizing PM
behavior without recreating the session. behavior without recreating the session.
Notifications:
PM Agent writes task completion notifications to ~/.kugetsu/notifications.json
Use 'kugetsu notify list' to see unread notifications.
Examples: Examples:
kugetsu init kugetsu init
kugetsu status kugetsu status
kugetsu delegate "work on issue #5" kugetsu delegate "work on issue #5"
kugetsu doctor kugetsu doctor
kugetsu doctor --fix kugetsu doctor --fix
kugetsu notify list
kugetsu notify clear
kugetsu start github.com/shoko/kugetsu#14 "fix bug" kugetsu start github.com/shoko/kugetsu#14 "fix bug"
kugetsu continue github.com/shoko/kugetsu#14 "add tests" kugetsu continue github.com/shoko/kugetsu#14 "add tests"
kugetsu list kugetsu list
@@ -305,6 +315,143 @@ kugetsu_get_pm_context() {
fi fi
} }
kugetsu_add_notification() {
local type="$1"
local message="$2"
local issue_ref="${3:-}"
local gitea_url="${4:-}"
mkdir -p "$(dirname "$NOTIFICATIONS_FILE")"
local notification=$(python3 << PYEOF
import json
import os
from datetime import datetime
notification = {
"type": "$type",
"message": "$message",
"issue_ref": "$issue_ref" if "$issue_ref" else None,
"gitea_url": "$gitea_url" if "$gitea_url" else None,
"timestamp": datetime.now().isoformat(),
"read": False
}
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
notifications = []
if os.path.exists(file_path):
try:
with open(file_path, 'r') as f:
notifications = json.load(f)
except:
notifications = []
notifications.append(notification)
with open(file_path, 'w') as f:
json.dump(notifications, f, indent=2)
print("Notification added")
PYEOF
)
echo "$notification"
}
kugetsu_get_notifications() {
local limit="${1:-10}"
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
echo "[]"
return
fi
python3 << PYEOF
import json
import os
from datetime import datetime
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
if not os.path.exists(file_path):
print("[]")
exit(0)
try:
with open(file_path, 'r') as f:
notifications = json.load(f)
unread = [n for n in notifications if not n.get("read", False)]
unread.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
for n in unread[:$limit]:
ts = n.get("timestamp", "unknown")
ntype = n.get("type", "info")
msg = n.get("message", "")
issue = n.get("issue_ref", "")
gitea = n.get("gitea_url", "")
print(f"[{ts}] {ntype}: {msg}")
if issue:
print(f" Issue: {issue}")
if gitea:
print(f" Link: {gitea}")
print()
if not unread:
print("No unread notifications.")
except Exception as e:
print(f"Error reading notifications: {e}")
PYEOF
}
kugetsu_clear_notifications() {
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
return
fi
python3 << PYEOF
import json
import os
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
if not os.path.exists(file_path):
exit(0)
try:
with open(file_path, 'r') as f:
notifications = json.load(f)
for n in notifications:
n["read"] = True
with open(file_path, 'w') as f:
json.dump(notifications, f, indent=2)
print("Notifications marked as read")
except Exception as e:
print(f"Error: {e}")
PYEOF
}
cmd_notify() {
local action="${1:-}"
case "$action" in
""|"list"|"show")
kugetsu_get_notifications 10
;;
"clear")
kugetsu_clear_notifications
;;
*)
echo "Usage: kugetsu notify [list|clear]"
;;
esac
}
cmd_status() { cmd_status() {
if [ ! -f "$INDEX_FILE" ]; then if [ ! -f "$INDEX_FILE" ]; then
echo "kugetsu_not_initialized" echo "kugetsu_not_initialized"
@@ -978,6 +1125,9 @@ main() {
doctor) doctor)
cmd_doctor "$@" cmd_doctor "$@"
;; ;;
notify)
cmd_notify "$@"
;;
list) list)
cmd_list "$@" cmd_list "$@"
;; ;;