diff --git a/.agents/scripts/auto-update-helper.sh b/.agents/scripts/auto-update-helper.sh index f588f8c92..984ecb435 100755 --- a/.agents/scripts/auto-update-helper.sh +++ b/.agents/scripts/auto-update-helper.sh @@ -40,7 +40,7 @@ readonly STATE_FILE="$HOME/.aidevops/cache/auto-update-state.json" readonly CRON_MARKER="# aidevops-auto-update" readonly DEFAULT_INTERVAL=10 readonly DEFAULT_SKILL_FRESHNESS_HOURS=24 -readonly LAUNCHD_LABEL="com.aidevops.auto-update" +readonly LAUNCHD_LABEL="com.aidevops.aidevops-auto-update" readonly LAUNCHD_DIR="$HOME/Library/LaunchAgents" readonly LAUNCHD_PLIST="${LAUNCHD_DIR}/${LAUNCHD_LABEL}.plist" @@ -573,6 +573,15 @@ cmd_enable() { if [[ "$backend" == "launchd" ]]; then local interval_seconds=$((interval * 60)) + # Migrate from old label if present (t1260) + local old_label="com.aidevops.auto-update" + local old_plist="${LAUNCHD_DIR}/${old_label}.plist" + if launchctl list 2>/dev/null | grep -qF "$old_label"; then + launchctl unload -w "$old_plist" 2>/dev/null || true + log_info "Unloaded old LaunchAgent: $old_label" + fi + rm -f "$old_plist" + # Auto-migrate existing cron entry if present _migrate_cron_to_launchd "$script_path" "$interval_seconds" @@ -584,7 +593,15 @@ cmd_enable() { fi mkdir -p "$LAUNCHD_DIR" - _generate_auto_update_plist "$script_path" "$interval_seconds" "${PATH}" >"$LAUNCHD_PLIST" + + # Create named symlink so macOS System Settings shows "aidevops-auto-update" + # instead of the raw script name (t1260) + local bin_dir="$HOME/.aidevops/bin" + mkdir -p "$bin_dir" + local display_link="$bin_dir/aidevops-auto-update" + ln -sf "$script_path" "$display_link" + + _generate_auto_update_plist "$display_link" "$interval_seconds" "${PATH}" >"$LAUNCHD_PLIST" if launchctl load -w "$LAUNCHD_PLIST" 2>/dev/null; then update_state "enable" "$(get_local_version)" "enabled" @@ -861,7 +878,7 @@ ENVIRONMENT: AIDEVOPS_SKILL_FRESHNESS_HOURS=24 Hours between skill checks (default: 24) SCHEDULER BACKENDS: - macOS: launchd LaunchAgent (~/Library/LaunchAgents/com.aidevops.auto-update.plist) + macOS: launchd LaunchAgent (~/Library/LaunchAgents/com.aidevops.aidevops-auto-update.plist) - Native macOS scheduler, survives reboots without cron - Auto-migrates existing cron entries on first 'enable' Linux: cron (crontab entry with # aidevops-auto-update marker) diff --git a/.agents/scripts/supervisor/cron.sh b/.agents/scripts/supervisor/cron.sh index 869d9a7e6..a53a6ab16 100755 --- a/.agents/scripts/supervisor/cron.sh +++ b/.agents/scripts/supervisor/cron.sh @@ -4,7 +4,7 @@ # Platform-aware: uses launchd on macOS, cron on Linux. # Same CLI interface (cron install/uninstall/status) regardless of backend. # -# On macOS: generates ~/Library/LaunchAgents/com.aidevops.supervisor-pulse.plist +# On macOS: generates ~/Library/LaunchAgents/com.aidevops.aidevops-supervisor-pulse.plist # On Linux: installs a crontab entry (unchanged behaviour) # Source launchd helpers (macOS backend) @@ -103,7 +103,7 @@ _cmd_cron_launchd() { "$batch_arg" # Install (migrate handles the case where cron existed; install handles fresh) - if ! _launchd_is_loaded "com.aidevops.supervisor-pulse"; then + if ! _launchd_is_loaded "com.aidevops.aidevops-supervisor-pulse"; then launchd_install_supervisor_pulse \ "$script_path" \ "$interval_seconds" \ @@ -167,20 +167,14 @@ _cmd_cron_linux() { # Detect current PATH for cron environment (t1006) local user_path="${PATH}" - # Detect GH_TOKEN from gh CLI if available (t1006) - local gh_token="" - if command -v gh &>/dev/null; then - gh_token=$(gh auth token 2>/dev/null || true) - fi + # GH_TOKEN is resolved at runtime by pulse.sh (t1260) + # Previously baked into crontab/plist as plaintext — no longer needed here # Build cron command with environment variables local env_vars="" if [[ -n "$user_path" ]]; then env_vars="PATH=${user_path}" fi - if [[ -n "$gh_token" ]]; then - env_vars="${env_vars:+${env_vars} }GH_TOKEN=${gh_token}" - fi local cron_cmd="*/${interval} * * * * ${env_vars:+${env_vars} }${script_path} pulse ${batch_arg} >> ${log_path} 2>&1 ${cron_marker}" diff --git a/.agents/scripts/supervisor/launchd.sh b/.agents/scripts/supervisor/launchd.sh index 270b25a41..3ef6c0060 100755 --- a/.agents/scripts/supervisor/launchd.sh +++ b/.agents/scripts/supervisor/launchd.sh @@ -5,9 +5,12 @@ # Called by cron.sh when running on macOS. # # Three LaunchAgent plists managed: -# com.aidevops.supervisor-pulse - StartInterval:120 (every 2 min) -# com.aidevops.auto-update - StartInterval:600 (every 10 min) -# com.aidevops.todo-watcher - WatchPaths (replaces fswatch) +# com.aidevops.aidevops-supervisor-pulse - StartInterval:120 (every 2 min) +# com.aidevops.aidevops-auto-update - StartInterval:600 (every 10 min) +# com.aidevops.aidevops-todo-watcher - WatchPaths (replaces fswatch) +# +# Labels are prefixed with "aidevops-" so they appear grouped in +# macOS System Settings > Login Items & Extensions. # # Migration: auto-migrates existing cron entries to launchd on macOS. @@ -35,7 +38,7 @@ _launchd_dir() { ####################################### # Plist path for a given label # Arguments: -# $1 - label (e.g., com.aidevops.supervisor-pulse) +# $1 - label (e.g., com.aidevops.aidevops-supervisor-pulse) ####################################### _plist_path() { local label="$1" @@ -86,7 +89,7 @@ _launchd_unload() { # $3 - log_path # $4 - batch_arg (optional, e.g., "--batch my-batch") # $5 - env_path (PATH value for launchd environment) -# $6 - gh_token (optional GH_TOKEN value) +# $6 - gh_token (deprecated — token now resolved at runtime by pulse.sh) ####################################### _generate_supervisor_pulse_plist() { local script_path="$1" @@ -96,7 +99,7 @@ _generate_supervisor_pulse_plist() { local env_path="${5:-/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin}" local gh_token="${6:-}" - local label="com.aidevops.supervisor-pulse" + local label="com.aidevops.aidevops-supervisor-pulse" # Build ProgramArguments array local prog_args @@ -167,7 +170,7 @@ _generate_auto_update_plist() { local log_path="$3" local env_path="${4:-/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin}" - local label="com.aidevops.auto-update" + local label="com.aidevops.aidevops-auto-update" cat < @@ -220,7 +223,7 @@ _generate_todo_watcher_plist() { local log_path="$4" local env_path="${5:-/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin}" - local label="com.aidevops.todo-watcher" + local label="com.aidevops.aidevops-todo-watcher" cat < @@ -273,7 +276,7 @@ launchd_install_supervisor_pulse() { local log_path="$3" local batch_arg="${4:-}" - local label="com.aidevops.supervisor-pulse" + local label="com.aidevops.aidevops-supervisor-pulse" local plist_path plist_path="$(_plist_path "$label")" local launchd_dir @@ -281,12 +284,26 @@ launchd_install_supervisor_pulse() { mkdir -p "$launchd_dir" - # Detect PATH and GH_TOKEN for launchd environment - local env_path="${PATH}" - local gh_token="" - if command -v gh &>/dev/null; then - gh_token=$(gh auth token 2>/dev/null || true) + # Migrate from old label if present (t1260) + local old_label="com.aidevops.supervisor-pulse" + local old_plist + old_plist="$(_plist_path "$old_label")" + if _launchd_is_loaded "$old_label"; then + _launchd_unload "$old_plist" || true + log_info "Unloaded old LaunchAgent: $old_label" fi + rm -f "$old_plist" + + # Detect PATH for launchd environment + # GH_TOKEN is resolved at runtime by the pulse script (not baked into plist) + local env_path="${PATH}" + + # Create named symlink so macOS System Settings shows "aidevops-supervisor-pulse" + # instead of the raw script name (t1260) + local bin_dir="$HOME/.aidevops/bin" + mkdir -p "$bin_dir" + local display_link="$bin_dir/aidevops-supervisor-pulse" + ln -sf "$script_path" "$display_link" # Check if already loaded if _launchd_is_loaded "$label"; then @@ -295,14 +312,14 @@ launchd_install_supervisor_pulse() { return 0 fi - # Generate and write plist + # Generate and write plist using symlink for display name (no GH_TOKEN — resolved at runtime) _generate_supervisor_pulse_plist \ - "$script_path" \ + "$display_link" \ "$interval_seconds" \ "$log_path" \ "$batch_arg" \ "$env_path" \ - "$gh_token" >"$plist_path" + "" >"$plist_path" # Load into launchd if _launchd_load "$plist_path"; then @@ -321,7 +338,7 @@ launchd_install_supervisor_pulse() { # Uninstall supervisor-pulse LaunchAgent on macOS ####################################### launchd_uninstall_supervisor_pulse() { - local label="com.aidevops.supervisor-pulse" + local label="com.aidevops.aidevops-supervisor-pulse" local plist_path plist_path="$(_plist_path "$label")" @@ -343,7 +360,7 @@ launchd_uninstall_supervisor_pulse() { # Show status of supervisor-pulse LaunchAgent ####################################### launchd_status_supervisor_pulse() { - local label="com.aidevops.supervisor-pulse" + local label="com.aidevops.aidevops-supervisor-pulse" local plist_path plist_path="$(_plist_path "$label")" @@ -392,7 +409,7 @@ launchd_install_auto_update() { local interval_seconds="${2:-600}" local log_path="$3" - local label="com.aidevops.auto-update" + local label="com.aidevops.aidevops-auto-update" local plist_path plist_path="$(_plist_path "$label")" local launchd_dir @@ -400,17 +417,34 @@ launchd_install_auto_update() { mkdir -p "$launchd_dir" + # Migrate from old label if present (t1260) + local old_label="com.aidevops.auto-update" + local old_plist + old_plist="$(_plist_path "$old_label")" + if _launchd_is_loaded "$old_label"; then + _launchd_unload "$old_plist" || true + log_info "Unloaded old LaunchAgent: $old_label" + fi + rm -f "$old_plist" + local env_path="${PATH}" + # Create named symlink so macOS System Settings shows "aidevops-auto-update" + # instead of the raw script name (t1260) + local bin_dir="$HOME/.aidevops/bin" + mkdir -p "$bin_dir" + local display_link="$bin_dir/aidevops-auto-update" + ln -sf "$script_path" "$display_link" + # Check if already loaded if _launchd_is_loaded "$label"; then log_warn "LaunchAgent $label already loaded. Unload first to change settings." return 0 fi - # Generate and write plist + # Generate and write plist using symlink for display name _generate_auto_update_plist \ - "$script_path" \ + "$display_link" \ "$interval_seconds" \ "$log_path" \ "$env_path" >"$plist_path" @@ -432,7 +466,7 @@ launchd_install_auto_update() { # Uninstall auto-update LaunchAgent on macOS ####################################### launchd_uninstall_auto_update() { - local label="com.aidevops.auto-update" + local label="com.aidevops.aidevops-auto-update" local plist_path plist_path="$(_plist_path "$label")" @@ -454,7 +488,7 @@ launchd_uninstall_auto_update() { # Show status of auto-update LaunchAgent ####################################### launchd_status_auto_update() { - local label="com.aidevops.auto-update" + local label="com.aidevops.aidevops-auto-update" local plist_path plist_path="$(_plist_path "$label")" @@ -499,7 +533,7 @@ launchd_install_todo_watcher() { local repo_path="$3" local log_path="$4" - local label="com.aidevops.todo-watcher" + local label="com.aidevops.aidevops-todo-watcher" local plist_path plist_path="$(_plist_path "$label")" local launchd_dir @@ -507,17 +541,34 @@ launchd_install_todo_watcher() { mkdir -p "$launchd_dir" + # Migrate from old label if present (t1260) + local old_label="com.aidevops.todo-watcher" + local old_plist + old_plist="$(_plist_path "$old_label")" + if _launchd_is_loaded "$old_label"; then + _launchd_unload "$old_plist" || true + log_info "Unloaded old LaunchAgent: $old_label" + fi + rm -f "$old_plist" + local env_path="${PATH}" + # Create named symlink so macOS System Settings shows "aidevops-todo-watcher" + # instead of the raw script name (t1260) + local bin_dir="$HOME/.aidevops/bin" + mkdir -p "$bin_dir" + local display_link="$bin_dir/aidevops-todo-watcher" + ln -sf "$script_path" "$display_link" + # Check if already loaded if _launchd_is_loaded "$label"; then log_warn "LaunchAgent $label already loaded." return 0 fi - # Generate and write plist + # Generate and write plist using symlink for display name _generate_todo_watcher_plist \ - "$script_path" \ + "$display_link" \ "$todo_path" \ "$repo_path" \ "$log_path" \ @@ -540,7 +591,7 @@ launchd_install_todo_watcher() { # Uninstall todo-watcher LaunchAgent on macOS ####################################### launchd_uninstall_todo_watcher() { - local label="com.aidevops.todo-watcher" + local label="com.aidevops.aidevops-todo-watcher" local plist_path plist_path="$(_plist_path "$label")" diff --git a/.agents/scripts/supervisor/pulse.sh b/.agents/scripts/supervisor/pulse.sh index 97414d5a6..68fe1e049 100755 --- a/.agents/scripts/supervisor/pulse.sh +++ b/.agents/scripts/supervisor/pulse.sh @@ -627,6 +627,24 @@ cmd_pulse() { ensure_db + # Resolve GH_TOKEN at runtime if not already set (t1260) + # Previously baked into the launchd plist as plaintext — now resolved + # from gh CLI keychain, gopass, or credential-helper at each pulse. + if [[ -z "${GH_TOKEN:-}" ]]; then + local _resolved_token="" + # Try gh CLI (reads from macOS Keychain when user is logged in) + if command -v gh &>/dev/null; then + _resolved_token=$(gh auth token 2>/dev/null || true) + fi + # Fallback: gopass encrypted store + if [[ -z "$_resolved_token" ]] && command -v gopass &>/dev/null; then + _resolved_token=$(gopass show -o "aidevops/GH_TOKEN" 2>/dev/null || true) + fi + if [[ -n "$_resolved_token" ]]; then + export GH_TOKEN="$_resolved_token" + fi + fi + # Acquire pulse dispatch lock to prevent concurrent pulses from # independently dispatching workers and exceeding concurrency limits (t159) if ! acquire_pulse_lock; then diff --git a/setup.sh b/setup.sh index 6affe188c..5abe68f22 100755 --- a/setup.sh +++ b/setup.sh @@ -519,9 +519,32 @@ main() { print_success "🎉 Setup complete!" # Enable auto-update if not already enabled + # Check both launchd (macOS) and cron (Linux) for existing installation local auto_update_script="$HOME/.aidevops/agents/scripts/auto-update-helper.sh" if [[ -x "$auto_update_script" ]] && [[ "${AIDEVOPS_AUTO_UPDATE:-true}" != "false" ]]; then - if ! crontab -l 2>/dev/null | grep -q "aidevops-auto-update"; then + local _auto_update_installed=false + if launchctl list 2>/dev/null | grep -qF "com.aidevops.aidevops-auto-update"; then + _auto_update_installed=true + elif launchctl list 2>/dev/null | grep -qF "com.aidevops.auto-update"; then + # Old label — re-running enable will migrate to new label + if bash "$auto_update_script" enable >/dev/null 2>&1; then + print_info "Auto-update LaunchAgent migrated to new label" + else + print_warning "Auto-update label migration failed. Run: aidevops auto-update enable" + fi + _auto_update_installed=true + elif crontab -l 2>/dev/null | grep -qF "aidevops-auto-update"; then + if [[ "$(uname -s)" == "Darwin" ]]; then + # macOS: cron entry exists but no launchd plist — migrate + if bash "$auto_update_script" enable >/dev/null 2>&1; then + print_info "Auto-update migrated from cron to launchd" + else + print_warning "Auto-update cron→launchd migration failed. Run: aidevops auto-update enable" + fi + fi + _auto_update_installed=true + fi + if [[ "$_auto_update_installed" == "false" ]]; then if [[ "$NON_INTERACTIVE" == "true" ]]; then # Non-interactive: enable silently bash "$auto_update_script" enable >/dev/null 2>&1 || true @@ -541,6 +564,51 @@ main() { fi fi + # Enable supervisor pulse scheduler if not already installed + # This is REQUIRED for autonomous task orchestration (dispatch, evaluation, cleanup) + local supervisor_script="$HOME/.aidevops/agents/scripts/supervisor-helper.sh" + if [[ -x "$supervisor_script" ]] && [[ "${AIDEVOPS_SUPERVISOR_PULSE:-true}" != "false" ]]; then + local _pulse_installed=false + if launchctl list 2>/dev/null | grep -qF "com.aidevops.aidevops-supervisor-pulse"; then + _pulse_installed=true + elif launchctl list 2>/dev/null | grep -qF "com.aidevops.supervisor-pulse"; then + # Old label — re-running install will migrate to new label + if bash "$supervisor_script" cron install >/dev/null 2>&1; then + print_info "Supervisor pulse LaunchAgent migrated to new label" + else + print_warning "Supervisor pulse label migration failed. Run: supervisor-helper.sh cron install" + fi + _pulse_installed=true + elif crontab -l 2>/dev/null | grep -qF "aidevops-supervisor-pulse"; then + if [[ "$(uname -s)" == "Darwin" ]]; then + # macOS: cron entry exists but no launchd plist — migrate + if bash "$supervisor_script" cron install >/dev/null 2>&1; then + print_info "Supervisor pulse migrated from cron to launchd" + else + print_warning "Supervisor pulse cron→launchd migration failed. Run: supervisor-helper.sh cron install" + fi + fi + _pulse_installed=true + fi + if [[ "$_pulse_installed" == "false" ]]; then + if [[ "$NON_INTERACTIVE" == "true" ]]; then + bash "$supervisor_script" cron install >/dev/null 2>&1 || true + print_info "Supervisor pulse enabled (every 2 min). Disable: supervisor-helper.sh cron uninstall" + else + echo "" + echo "The supervisor pulse runs every 2 minutes to dispatch tasks, evaluate" + echo "worker results, merge PRs, and clean up. Required for autonomous operation." + echo "" + read -r -p "Enable supervisor pulse? [Y/n]: " enable_pulse + if [[ "$enable_pulse" =~ ^[Yy]?$ || -z "$enable_pulse" ]]; then + bash "$supervisor_script" cron install + else + print_info "Skipped. Enable later: supervisor-helper.sh cron install" + fi + fi + fi + fi + echo "" echo "CLI Command:" echo " aidevops init - Initialize aidevops in a project"