Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions .agents/scripts/headless-runtime-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ get_session_id() {
return 0
}

clear_session_id() {
local provider="$1"
local session_key="$2"
db_query "DELETE FROM provider_sessions WHERE provider = '$(sql_escape "$provider")' AND session_key = '$(sql_escape "$session_key")';" >/dev/null
return 0
}

store_session_id() {
local provider="$1"
local session_key="$2"
Expand Down Expand Up @@ -671,10 +678,18 @@ cmd_run() {
return 1
}

local selected_model provider persisted_session
local selected_model provider persisted_session=""
selected_model=$(choose_model "$role" "$model_override") || return $?
provider=$(extract_provider "$selected_model")
persisted_session=$(get_session_id "$provider" "$session_key")
if [[ "$role" == "pulse" ]]; then
# Pulse runs must start from the current pre-fetched state each cycle.
# Reusing a prior OpenCode session contaminates later /pulse runs with
# stale conversational context, which leads to idle watchdog kills and an
# empty worker pool. Workers still keep session reuse.
clear_session_id "$provider" "$session_key"
else
persisted_session=$(get_session_id "$provider" "$session_key")
fi

local -a cmd=("$OPENCODE_BIN_DEFAULT" run "$prompt" --dir "$work_dir" -m "$selected_model" --title "$title" --format json)
if [[ -n "$agent_name" ]]; then
Expand Down Expand Up @@ -704,7 +719,7 @@ cmd_run() {
print_warning "$provider returned exit 0 without any model activity; backing off provider"
return 75
fi
if [[ -n "$discovered_session" ]]; then
if [[ "$role" != "pulse" && -n "$discovered_session" ]]; then
store_session_id "$provider" "$session_key" "$discovered_session" "$selected_model"
fi
rm -f "$output_file"
Expand Down
22 changes: 19 additions & 3 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ _cron_escape() {
return 0
}

# Resolve the canonical main worktree path for the current repo.
# When setup.sh is run from a linked worktree, launchd/cron should still point
# autonomous services at the main repo checkout, not the feature worktree.
_resolve_main_worktree_dir() {
local repo_dir="$1"
local main_worktree=""
main_worktree=$(git -C "$repo_dir" worktree list --porcelain 2>/dev/null | awk '/^worktree / {print substr($0, 10); exit}') || main_worktree=""
if [[ -n "$main_worktree" && -d "$main_worktree" ]]; then
printf '%s' "$main_worktree"
return 0
fi
printf '%s' "$repo_dir"
return 0
}

# Ensure the crontab has a single PATH= line at the top with the current $PATH.
# Individual cron entries must NOT set inline PATH= — it overrides the global one
# and hardcodes system-specific paths (nvm, bun, cargo, etc.). This function
Expand Down Expand Up @@ -840,8 +855,9 @@ main() {
# - Non-interactive: only installs if config explicitly says true
local wrapper_script="$HOME/.aidevops/agents/scripts/pulse-wrapper.sh"
local pulse_label="com.aidevops.aidevops-supervisor-pulse"
local _aidevops_dir
local _aidevops_dir _pulse_repo_dir
_aidevops_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_pulse_repo_dir=$(_resolve_main_worktree_dir "$_aidevops_dir")

# Read explicit user consent from config.jsonc (not merged defaults).
# Empty = user never configured this; "true"/"false" = explicit choice.
Expand Down Expand Up @@ -959,7 +975,7 @@ main() {
_xml_wrapper_script=$(_xml_escape "$wrapper_script")
_xml_home=$(_xml_escape "$HOME")
_xml_opencode_bin=$(_xml_escape "$opencode_bin")
_xml_aidevops_dir=$(_xml_escape "$_aidevops_dir")
_xml_aidevops_dir=$(_xml_escape "$_pulse_repo_dir")
_xml_path=$(_xml_escape "$PATH")
if [[ -n "${AIDEVOPS_HEADLESS_MODELS:-}" ]]; then
local _xml_headless_models
Expand Down Expand Up @@ -1035,7 +1051,7 @@ PLIST
# via $(…) or backticks if paths contain shell metacharacters
local _cron_opencode_bin _cron_aidevops_dir _cron_wrapper_script _cron_headless_env=""
_cron_opencode_bin=$(_cron_escape "$opencode_bin")
_cron_aidevops_dir=$(_cron_escape "$_aidevops_dir")
_cron_aidevops_dir=$(_cron_escape "$_pulse_repo_dir")
_cron_wrapper_script=$(_cron_escape "$wrapper_script")
if [[ -n "${AIDEVOPS_HEADLESS_MODELS:-}" ]]; then
local _cron_headless_models
Expand Down
23 changes: 23 additions & 0 deletions tests/test-headless-runtime-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,29 @@ else
fail "second run reuses persisted provider session" "logged args: $(tr '\n' ' ' <"$STUB_LOG_FILE")"
fi

section "Pulse Runs Stay Fresh"
export STUB_SESSION_ID="ses_pulse_one"
rm -f "$STUB_LOG_FILE"
AIDEVOPS_HEADLESS_PROVIDER_ALLOWLIST=openai bash "$HELPER" run \
--role pulse \
--session-key supervisor-pulse \
--dir "$REPO_DIR" \
--title "Supervisor Pulse" \
--prompt "/pulse" >/dev/null
export STUB_SESSION_ID="ses_pulse_two"
AIDEVOPS_HEADLESS_PROVIDER_ALLOWLIST=openai bash "$HELPER" run \
--role pulse \
--session-key supervisor-pulse \
--dir "$REPO_DIR" \
--title "Supervisor Pulse" \
--prompt "/pulse" >/dev/null

if grep -q -- '--session ' "$STUB_LOG_FILE"; then
fail "pulse runs do not reuse persisted sessions" "logged args: $(tr '\n' ' ' <"$STUB_LOG_FILE")"
else
pass "pulse runs do not reuse persisted sessions"
fi

section "Zero Activity Success Is Rejected"
export STUB_EMIT_ACTIVITY="0"
if AIDEVOPS_HEADLESS_PROVIDER_ALLOWLIST=openai bash "$HELPER" run \
Expand Down
Loading