Compare commits

...

6 Commits

Author SHA1 Message Date
shokollm
5a0a54898b fix(kugetsu): kugetsu-session.sh needs to source required modules
When daemon sources kugetsu-session.sh to call cmd_start/cmd_continue,
it needs access to functions from kugetsu-config.sh, kugetsu-index.sh,
kugetsu-worktree.sh, and kugetsu-log.sh. Add sourcing at top of
kugetsu-session.sh.
2026-04-05 22:20:49 +00:00
shokollm
b1028a6556 fix(kugetsu): move queue functions to kugetsu-index.sh for daemon access
The daemon (kugetsu-queue-daemon.sh) sources kugetsu-index.sh but not the main kugetsu script.
Move update_queue_item_state and kugetsu_add_notification to kugetsu-index.sh
so the daemon can use these functions when processing tasks.
2026-04-05 22:17:59 +00:00
shokollm
270219873f fix(kugetsu): cmd_delegate should enqueue instead of calling cmd_start
When cmd_delegate detects an issue ref with number (e.g. git.fbrns.co/shoko/kugetsu#158),
it was calling cmd_start directly which tries to create worktree and clone.
This breaks the queue-based workflow where daemon should handle task execution.

Now cmd_delegate calls enqueue_task to add to queue, and daemon processes
tasks by calling cmd_start/cmd_continue as appropriate.
2026-04-05 22:05:18 +00:00
shokollm
cbfc8a0646 refactor(kugetsu): daemon uses cmd_start/cmd_continue instead of direct opencode calls
- Move issue_ref_to_filename and filename_to_issue_ref to kugetsu-index.sh
  (where they logically belong, instead of in main kugetsu script)
- Refactor queue daemon to use cmd_start/cmd_continue for session management
- Daemon now checks if worktree/session exists → cmd_continue, else → cmd_start
- Removes ~40 lines of direct opencode session forking logic from daemon
- cmd_start/cmd_continue handle worktree creation, session forking, and tracking

This simplifies the daemon significantly and centralizes session management
in kugetsu-session.sh where it belongs.
2026-04-05 21:29:34 +00:00
shokollm
7fa669b4c3 fix(kugetsu): queue daemon runs PM agent in correct worktree with proper token
- Load GITEA_TOKEN from ~/.kugetsu/env/default.env at daemon startup
- Use --fork --session --dir instead of --continue to run in correct directory
- Create worktree if it doesn't exist for the issue
- Track forked session ID (not parent pm_session) for completion detection
- Forked session ends when task completes, parent pm_session continues

Fixes #156
2026-04-05 20:57:51 +00:00
acb503471d Merge pull request 'fix(kugetsu): detect task completion and queue state updates' (#154) from fix/issue-150 into main 2026-04-05 15:10:48 +02:00
4 changed files with 142 additions and 22 deletions

View File

@@ -99,17 +99,6 @@ ensure_worktree_dir() {
mkdir -p "$WORKTREES_DIR" mkdir -p "$WORKTREES_DIR"
} }
issue_ref_to_filename() {
local issue_ref="$1"
echo "$issue_ref" | sed 's/[\/:]/-/g' | sed 's/#/-/'
}
filename_to_issue_ref() {
local filename="$1"
local name="${filename%.json}"
echo "$name" | sed 's/-\([0-9]*\)$/#\1/' | sed 's/-/\//g'
}
issue_ref_to_context_file() { issue_ref_to_context_file() {
local issue_ref="$1" local issue_ref="$1"
local context_filename=$(issue_ref_to_filename "$issue_ref") local context_filename=$(issue_ref_to_filename "$issue_ref")

View File

@@ -130,6 +130,111 @@ session['pr_url'] = pr_url
with open(session_path, 'w') as f: with open(session_path, 'w') as f:
json.dump(session, f, indent=2) json.dump(session, f, indent=2)
print(f"Updated PR URL for $issue_ref: $pr_url") print(f"Updated PR URL for $issue_ref: $pr_url")
PYEOF
}
# Convert issue ref to session filename
issue_ref_to_filename() {
local issue_ref="$1"
echo "$issue_ref" | sed 's/[\/:]/-/g' | sed 's/#/-/'
}
# Convert session filename back to issue ref
filename_to_issue_ref() {
local filename="$1"
local name="${filename%.json}"
echo "$name" | sed 's-\([0-9]*\)$-#\1-' | sed 's/-/\//g'
}
# Add notification to notifications file
kugetsu_add_notification() {
local type="$1"
local message="$2"
local issue_ref="${3:-}"
local gitea_url="${4:-}"
mkdir -p "$(dirname "$NOTIFICATIONS_FILE")"
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
}
# Update queue item state
update_queue_item_state() {
local queue_id="$1"
local new_state="$2"
local session_id="${3:-}"
local pid="${4:-}"
local item_file="$QUEUE_ITEMS_DIR/${queue_id}.json"
if [ ! -f "$item_file" ]; then
echo "Error: Queue item not found: $queue_id" >&2
return 1
fi
python3 << PYEOF
import json
import os
from datetime import datetime
item_file = "$item_file"
new_state = "$new_state"
session_id = "$session_id"
pid = "$pid"
with open(item_file, 'r') as f:
item = json.load(f)
issue_ref = item.get('issue_ref', '')
item['state'] = new_state
if new_state == "notified":
item['notified_at'] = datetime.now().isoformat() + "Z"
if session_id:
item['opencode_session_id'] = session_id
if pid:
item['pid'] = int(pid) if pid.isdigit() else None
elif new_state == "completed":
item['completed_at'] = datetime.now().isoformat() + "Z"
os.system(f"kugetsu_add_notification 'task_completed' 'Task completed: {issue_ref}' '{issue_ref}'")
elif new_state == "error":
item['error'] = datetime.now().isoformat() + "Z"
os.system(f"kugetsu_add_notification 'task_error' 'Task error: {issue_ref}' '{issue_ref}'")
with open(item_file, 'w') as f:
json.dump(item, f, indent=2)
print(f"Updated $queue_id to state: $new_state")
PYEOF PYEOF
} }

33
skills/kugetsu/scripts/kugetsu-queue-daemon.sh Executable file → Normal file
View File

@@ -8,7 +8,12 @@ source "$SCRIPT_DIR/kugetsu-index.sh"
source "$SCRIPT_DIR/kugetsu-worktree.sh" source "$SCRIPT_DIR/kugetsu-worktree.sh"
source "$SCRIPT_DIR/kugetsu-log.sh" source "$SCRIPT_DIR/kugetsu-log.sh"
# Check if a notified task has completed (session ended or has new commits) # Load GITEA_TOKEN from default.env
if [ -f "$HOME/.kugetsu/env/default.env" ]; then
source "$HOME/.kugetsu/env/default.env"
fi
# Check if a notified task has completed (forked session ended or has new commits)
check_task_completion() { check_task_completion() {
local item="$1" local item="$1"
local queue_id=$(basename "$item" .json) local queue_id=$(basename "$item" .json)
@@ -16,15 +21,16 @@ check_task_completion() {
[ "$state" = "notified" ] || return 0 [ "$state" = "notified" ] || return 0
local session_id=$(python3 -c "import json; print(json.load(open('$item')).get('session_id', ''))" 2>/dev/null) # Use opencode_session_id (the forked session, not the parent pm_session)
local session_id=$(python3 -c "import json; print(json.load(open('$item')).get('opencode_session_id', ''))" 2>/dev/null)
local issue_ref=$(python3 -c "import json; print(json.load(open('$item')).get('issue_ref', ''))" 2>/dev/null) local issue_ref=$(python3 -c "import json; print(json.load(open('$item')).get('issue_ref', ''))" 2>/dev/null)
# If no session tracked, skip # If no session tracked, skip
[ -n "$session_id" ] || return 0 [ -n "$session_id" ] || return 0
# Check if session still exists in opencode # Check if forked session still exists in opencode
if ! opencode session list 2>/dev/null | grep -q "$session_id"; then if ! opencode session list 2>/dev/null | grep -q "$session_id"; then
# Session ended — check if work was done # Forked session ended — check if work was done
local worktree_path=$(issue_ref_to_worktree_path "$issue_ref" "$HOME/.kugetsu-worktrees") local worktree_path=$(issue_ref_to_worktree_path "$issue_ref" "$HOME/.kugetsu-worktrees")
local has_commits=false local has_commits=false
@@ -62,12 +68,23 @@ while true; do
issue_ref=$(python3 -c "import json; print(json.load(open('$item')).get('issue_ref', ''))" 2>/dev/null) issue_ref=$(python3 -c "import json; print(json.load(open('$item')).get('issue_ref', ''))" 2>/dev/null)
message=$(python3 -c "import json; print(json.load(open('$item')).get('message', ''))" 2>/dev/null) message=$(python3 -c "import json; print(json.load(open('$item')).get('message', ''))" 2>/dev/null)
pm_session=$(get_pm_agent_session_id) # Source session management and use cmd_start/cmd_continue
if [ -n "$pm_session" ] && [ "$pm_session" != "null" ]; then source "$SCRIPT_DIR/kugetsu-session.sh"
if worktree_exists "$issue_ref" "$HOME/.kugetsu-worktrees" || [ -f "$SESSIONS_DIR/$(issue_ref_to_filename "$issue_ref").json" ]; then
# Continue existing session
log_file="$LOGS_DIR/delegate-$(date +%s).log" log_file="$LOGS_DIR/delegate-$(date +%s).log"
nohup env GITEA_TOKEN="$GITEA_TOKEN" sh -c "opencode run '$message' --continue --session '$pm_session' >> '$log_file' 2>&1" > /dev/null 2>&1 & cmd_continue "$issue_ref" "$message" >> "$log_file" 2>&1 &
pid=$! pid=$!
update_queue_item_state "$queue_id" "notified" "$pm_session" "$pid" update_queue_item_state "$queue_id" "notified" "" "$pid"
echo "Task $queue_id continued for $issue_ref"
else
# Start new session
log_file="$LOGS_DIR/delegate-$(date +%s).log"
cmd_start "$issue_ref" "$message" >> "$log_file" 2>&1 &
pid=$!
update_queue_item_state "$queue_id" "notified" "" "$pid"
echo "Task $queue_id started for $issue_ref"
fi fi
fi fi
done done

View File

@@ -1,6 +1,13 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
# Source required modules for session management functions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/kugetsu-config.sh"
source "$SCRIPT_DIR/kugetsu-index.sh"
source "$SCRIPT_DIR/kugetsu-worktree.sh"
source "$SCRIPT_DIR/kugetsu-log.sh"
count_active_dev_sessions() { count_active_dev_sessions() {
local count=0 local count=0
if [ -d "$SESSIONS_DIR" ]; then if [ -d "$SESSIONS_DIR" ]; then
@@ -153,10 +160,12 @@ cmd_delegate() {
local issue_ref=$(extract_issue_ref_from_message "$message") local issue_ref=$(extract_issue_ref_from_message "$message")
if [ -n "$issue_ref" ] && [[ "$issue_ref" =~ \#[0-9]+$ ]]; then if [ -n "$issue_ref" ] && [[ "$issue_ref" =~ \#[0-9]+$ ]]; then
cmd_start "$issue_ref" "$message" # Enqueue for daemon to process via cmd_start/cmd_continue
enqueue_task "$issue_ref" "$message"
return return
fi fi
# No issue ref detected — delegate directly to PM agent (legacy path)
local pm_session=$(get_pm_agent_session_id) local pm_session=$(get_pm_agent_session_id)
if [ -z "$pm_session" ] || [ "$pm_session" = "null" ] || [ "$pm_session" = "None" ]; then if [ -z "$pm_session" ] || [ "$pm_session" = "null" ] || [ "$pm_session" = "None" ]; then
echo "Error: PM agent session not found. Run 'kugetsu init' first." >&2 echo "Error: PM agent session not found. Run 'kugetsu init' first." >&2
@@ -165,7 +174,7 @@ cmd_delegate() {
mkdir -p "$LOGS_DIR" mkdir -p "$LOGS_DIR"
local log_file="$LOGS_DIR/delegate-$(date +%s).log" local log_file="$LOGS_DIR/delegate-$(date +%s).log"
nohup sh -c "GITEA_TOKEN='${GITEA_TOKEN:-}' opencode run '$message' --continue --session '$pm_session' >> '$log_file' 2>&1" > /dev/null 2>&1 & nohup sh -c "GITEA_TOKEN='***' opencode run '$message' --continue --session '$pm_session' >> '$log_file' 2>&1" > /dev/null 2>&1 &
disown disown
echo "Delegated to PM agent (logged to $(basename "$log_file"))" echo "Delegated to PM agent (logged to $(basename "$log_file"))"
} }