agent-control-plane 0.4.9 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -9
- package/npm/bin/agent-control-plane.js +1 -1
- package/package.json +39 -33
- package/tools/bin/debug-session.sh +106 -0
- package/tools/bin/flow-runtime-doctor-linux.sh +136 -0
- package/tools/bin/flow-runtime-doctor.sh +5 -1
- package/tools/bin/install-project-systemd.sh +255 -0
- package/tools/bin/project-runtimectl.sh +45 -0
- package/tools/bin/project-systemd-bootstrap.sh +74 -0
- package/tools/bin/uninstall-project-systemd.sh +87 -0
- package/tools/dashboard/app.js +198 -5
- package/tools/dashboard/issue_queue_state.py +101 -0
- package/tools/dashboard/server.py +123 -1
- package/tools/dashboard/styles.css +526 -455
- package/tools/bin/agent-cleanup-worktree +0 -247
- package/tools/bin/agent-github-update-labels +0 -105
- package/tools/bin/agent-init-worktree +0 -216
- package/tools/bin/agent-project-archive-run +0 -52
- package/tools/bin/agent-project-capture-worker +0 -46
- package/tools/bin/agent-project-catch-up-issue-pr-links +0 -118
- package/tools/bin/agent-project-catch-up-merged-prs +0 -195
- package/tools/bin/agent-project-catch-up-scheduled-issue-retries +0 -123
- package/tools/bin/agent-project-cleanup-session +0 -513
- package/tools/bin/agent-project-detached-launch +0 -127
- package/tools/bin/agent-project-heartbeat-loop +0 -1029
- package/tools/bin/agent-project-open-issue-worktree +0 -89
- package/tools/bin/agent-project-open-pr-worktree +0 -80
- package/tools/bin/agent-project-publish-issue-pr +0 -468
- package/tools/bin/agent-project-reconcile-issue-session +0 -1409
- package/tools/bin/agent-project-reconcile-pr-session +0 -1288
- package/tools/bin/agent-project-retry-state +0 -158
- package/tools/bin/agent-project-run-claude-session +0 -805
- package/tools/bin/agent-project-run-codex-resilient +0 -963
- package/tools/bin/agent-project-run-codex-session +0 -435
- package/tools/bin/agent-project-run-kilo-session +0 -369
- package/tools/bin/agent-project-run-ollama-session +0 -658
- package/tools/bin/agent-project-run-openclaw-session +0 -1309
- package/tools/bin/agent-project-run-opencode-session +0 -377
- package/tools/bin/agent-project-run-pi-session +0 -479
- package/tools/bin/agent-project-sync-anchor-repo +0 -139
- package/tools/bin/agent-project-sync-source-repo-main +0 -163
- package/tools/bin/agent-project-worker-status +0 -188
- package/tools/bin/branch-verification-guard.sh +0 -364
- package/tools/bin/capture-worker.sh +0 -18
- package/tools/bin/cleanup-worktree.sh +0 -52
- package/tools/bin/codex-quota +0 -31
- package/tools/bin/create-follow-up-issue.sh +0 -114
- package/tools/bin/dashboard-launchd-bootstrap.sh +0 -50
- package/tools/bin/issue-publish-localization-guard.sh +0 -142
- package/tools/bin/issue-publish-scope-guard.sh +0 -242
- package/tools/bin/issue-requires-local-workspace-install.sh +0 -31
- package/tools/bin/issue-resource-class.sh +0 -12
- package/tools/bin/kick-scheduler.sh +0 -75
- package/tools/bin/label-follow-up-issues.sh +0 -14
- package/tools/bin/new-pr-worktree.sh +0 -50
- package/tools/bin/new-worktree.sh +0 -49
- package/tools/bin/pr-risk.sh +0 -12
- package/tools/bin/prepare-worktree.sh +0 -142
- package/tools/bin/provider-cooldown-state.sh +0 -204
- package/tools/bin/publish-issue-worker.sh +0 -31
- package/tools/bin/reconcile-bootstrap-lib.sh +0 -113
- package/tools/bin/reconcile-issue-worker.sh +0 -34
- package/tools/bin/reconcile-pr-worker.sh +0 -34
- package/tools/bin/record-verification.sh +0 -71
- package/tools/bin/render-flow-config.sh +0 -98
- package/tools/bin/resident-issue-controller-lib.sh +0 -448
- package/tools/bin/retry-state.sh +0 -31
- package/tools/bin/reuse-issue-worktree.sh +0 -121
- package/tools/bin/run-codex-bypass.sh +0 -3
- package/tools/bin/run-codex-safe.sh +0 -3
- package/tools/bin/run-codex-task.sh +0 -280
- package/tools/bin/serve-dashboard.sh +0 -5
- package/tools/bin/start-issue-worker.sh +0 -943
- package/tools/bin/start-pr-fix-worker.sh +0 -528
- package/tools/bin/start-pr-merge-repair-worker.sh +0 -8
- package/tools/bin/start-pr-review-worker.sh +0 -261
- package/tools/bin/start-resident-issue-loop.sh +0 -499
- package/tools/bin/update-github-labels.sh +0 -14
- package/tools/bin/worker-status.sh +0 -19
- package/tools/bin/workflow-catalog.sh +0 -77
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'EOF'
|
|
6
|
+
Usage:
|
|
7
|
+
install-project-systemd.sh --profile-id <id> [options]
|
|
8
|
+
|
|
9
|
+
Install a per-user systemd service so one ACP project runtime starts
|
|
10
|
+
automatically after login/restart on Linux.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--profile-id <id> Installed profile id to manage
|
|
14
|
+
--unit-name <name> Override systemd unit name (default: agent-project-<id>.service)
|
|
15
|
+
--delay-seconds <n> Initial supervisor delay before first bootstrap (default: 0)
|
|
16
|
+
--interval-seconds <n> Supervisor interval between bootstrap passes (default: 15)
|
|
17
|
+
--help Show this help
|
|
18
|
+
EOF
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
|
+
# shellcheck source=/dev/null
|
|
23
|
+
source "${script_dir}/flow-config-lib.sh"
|
|
24
|
+
|
|
25
|
+
append_path_dir() {
|
|
26
|
+
local value_name="${1:?value name required}"
|
|
27
|
+
local candidate="${2:-}"
|
|
28
|
+
local current=""
|
|
29
|
+
|
|
30
|
+
[[ -n "${candidate}" && -d "${candidate}" ]] || return 0
|
|
31
|
+
current="${!value_name:-}"
|
|
32
|
+
case ":${current}:" in
|
|
33
|
+
*":${candidate}:"*) return 0 ;;
|
|
34
|
+
esac
|
|
35
|
+
if [[ -n "${current}" ]]; then
|
|
36
|
+
printf -v "${value_name}" '%s:%s' "${current}" "${candidate}"
|
|
37
|
+
else
|
|
38
|
+
printf -v "${value_name}" '%s' "${candidate}"
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
resolved_tool_dir() {
|
|
43
|
+
local tool_name="${1:-}"
|
|
44
|
+
local tool_path=""
|
|
45
|
+
|
|
46
|
+
[[ -n "${tool_name}" ]] || return 1
|
|
47
|
+
tool_path="$(command -v "${tool_name}" 2>/dev/null || true)"
|
|
48
|
+
[[ -n "${tool_path}" ]] || return 1
|
|
49
|
+
dirname "${tool_path}"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
build_systemd_base_path() {
|
|
53
|
+
local path_value="${ACP_PROJECT_RUNTIME_PATH:-/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin}"
|
|
54
|
+
local tool_name=""
|
|
55
|
+
local tool_dir=""
|
|
56
|
+
|
|
57
|
+
for tool_name in node gh git python3 openclaw codex claude ollama pi crush kilo; do
|
|
58
|
+
tool_dir="$(resolved_tool_dir "${tool_name}" || true)"
|
|
59
|
+
append_path_dir path_value "${tool_dir}"
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
printf '%s\n' "${path_value}"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
profile_id_override=""
|
|
66
|
+
unit_name_override=""
|
|
67
|
+
delay_seconds="0"
|
|
68
|
+
interval_seconds="15"
|
|
69
|
+
profile_registry_root_override="${ACP_PROJECT_RUNTIME_PROFILE_REGISTRY_ROOT:-${ACP_PROFILE_REGISTRY_ROOT:-}}"
|
|
70
|
+
|
|
71
|
+
while [[ $# -gt 0 ]]; do
|
|
72
|
+
case "$1" in
|
|
73
|
+
--profile-id) profile_id_override="${2:-}"; shift 2 ;;
|
|
74
|
+
--unit-name) unit_name_override="${2:-}"; shift 2 ;;
|
|
75
|
+
--delay-seconds) delay_seconds="${2:-}"; shift 2 ;;
|
|
76
|
+
--interval-seconds) interval_seconds="${2:-}"; shift 2 ;;
|
|
77
|
+
--help|-h) usage; exit 0 ;;
|
|
78
|
+
*) echo "Unknown argument: $1" >&2; usage >&2; exit 64 ;;
|
|
79
|
+
esac
|
|
80
|
+
done
|
|
81
|
+
|
|
82
|
+
if [[ -z "${profile_id_override}" ]]; then
|
|
83
|
+
usage >&2
|
|
84
|
+
exit 64
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
case "${delay_seconds}" in
|
|
88
|
+
''|*[!0-9]*) echo "--delay-seconds must be numeric" >&2; exit 64 ;;
|
|
89
|
+
esac
|
|
90
|
+
|
|
91
|
+
case "${interval_seconds}" in
|
|
92
|
+
''|*[!0-9]*) echo "--interval-seconds must be numeric" >&2; exit 64 ;;
|
|
93
|
+
esac
|
|
94
|
+
|
|
95
|
+
export ACP_PROJECT_ID="${profile_id_override}"
|
|
96
|
+
export AGENT_PROJECT_ID="${profile_id_override}"
|
|
97
|
+
|
|
98
|
+
if [[ -n "${profile_registry_root_override}" ]]; then
|
|
99
|
+
export ACP_PROFILE_REGISTRY_ROOT="${profile_registry_root_override}"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
flow_skill_dir="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
|
|
103
|
+
if ! flow_require_explicit_profile_selection "${flow_skill_dir}" "install-project-systemd.sh"; then
|
|
104
|
+
exit 64
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
config_yaml="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
108
|
+
if [[ ! -f "${config_yaml}" ]]; then
|
|
109
|
+
printf 'profile not installed: %s\n' "${profile_id_override}" >&2
|
|
110
|
+
exit 66
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
profile_id="$(flow_resolve_adapter_id "${config_yaml}")"
|
|
114
|
+
profile_slug="$(printf '%s' "${profile_id}" | tr -c 'A-Za-z0-9._-' '-')"
|
|
115
|
+
home_dir="${ACP_PROJECT_RUNTIME_HOME_DIR:-${HOME:-}}"
|
|
116
|
+
source_home="${ACP_PROJECT_RUNTIME_SOURCE_HOME:-}"
|
|
117
|
+
|
|
118
|
+
if [[ -z "${source_home}" ]]; then
|
|
119
|
+
if flow_is_skill_root "${flow_skill_dir}"; then
|
|
120
|
+
source_home="${flow_skill_dir}"
|
|
121
|
+
else
|
|
122
|
+
source_home="$(cd "${flow_skill_dir}/../../.." && pwd)"
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
runtime_home="${ACP_PROJECT_RUNTIME_RUNTIME_HOME:-${home_dir}/.agent-runtime/runtime-home}"
|
|
127
|
+
workspace_dir="${ACP_PROJECT_RUNTIME_WORKSPACE_DIR:-${home_dir}/.agent-runtime/control-plane/workspace}"
|
|
128
|
+
profile_registry_root="${ACP_PROJECT_RUNTIME_PROFILE_REGISTRY_ROOT:-${ACP_PROFILE_REGISTRY_ROOT:-${home_dir}/.agent-runtime/control-plane/profiles}}"
|
|
129
|
+
systemd_dir="${ACP_PROJECT_RUNTIME_SYSTEMD_DIR:-${home_dir}/.config/systemd/user}"
|
|
130
|
+
log_dir="${ACP_PROJECT_RUNTIME_LOG_DIR:-${home_dir}/.agent-runtime/logs}"
|
|
131
|
+
unit_name="${unit_name_override:-${ACP_PROJECT_RUNTIME_SYSTEMD_UNIT:-agent-project-${profile_slug}.service}}"
|
|
132
|
+
base_path="$(build_systemd_base_path)"
|
|
133
|
+
coding_worker_override="${ACP_PROJECT_RUNTIME_CODING_WORKER:-${ACP_CODING_WORKER:-}}"
|
|
134
|
+
sync_script="${ACP_PROJECT_RUNTIME_SYNC_SCRIPT:-${flow_skill_dir}/tools/bin/sync-shared-agent-home.sh}"
|
|
135
|
+
runtime_skill_dir="${runtime_home}/skills/openclaw/agent-control-plane"
|
|
136
|
+
bootstrap_script="${ACP_PROJECT_RUNTIME_BOOTSTRAP_SCRIPT:-}"
|
|
137
|
+
|
|
138
|
+
if [[ -z "${bootstrap_script}" ]]; then
|
|
139
|
+
if [[ -x "${runtime_skill_dir}/tools/bin/project-systemd-bootstrap.sh" ]]; then
|
|
140
|
+
bootstrap_script="${runtime_skill_dir}/tools/bin/project-systemd-bootstrap.sh"
|
|
141
|
+
else
|
|
142
|
+
bootstrap_script="${flow_skill_dir}/tools/bin/project-systemd-bootstrap.sh"
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
supervisor_script="${ACP_PROJECT_RUNTIME_SUPERVISOR_SCRIPT:-}"
|
|
147
|
+
if [[ -z "${supervisor_script}" ]]; then
|
|
148
|
+
if [[ -x "${runtime_skill_dir}/tools/bin/project-runtime-supervisor.sh" ]]; then
|
|
149
|
+
supervisor_script="${runtime_skill_dir}/tools/bin/project-runtime-supervisor.sh"
|
|
150
|
+
else
|
|
151
|
+
supervisor_script="${flow_skill_dir}/tools/bin/project-runtime-supervisor.sh"
|
|
152
|
+
fi
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
state_root="$(flow_resolve_state_root "${config_yaml}")"
|
|
156
|
+
supervisor_pid_file="${state_root}/runtime-supervisor.pid"
|
|
157
|
+
env_file="${ACP_PROJECT_RUNTIME_ENV_FILE:-${profile_registry_root}/${profile_id}/runtime.env}"
|
|
158
|
+
wrapper_path="${workspace_dir}/bin/agent-project-${profile_slug}-systemd.sh"
|
|
159
|
+
unit_file="${systemd_dir}/${unit_name}"
|
|
160
|
+
stdout_log="${log_dir}/agent-project-${profile_slug}.stdout.log"
|
|
161
|
+
stderr_log="${log_dir}/agent-project-${profile_slug}.stderr.log"
|
|
162
|
+
|
|
163
|
+
if [[ -z "${home_dir}" ]]; then
|
|
164
|
+
echo "install-project-systemd requires HOME or ACP_PROJECT_RUNTIME_HOME_DIR" >&2
|
|
165
|
+
exit 64
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# Check if systemd is available
|
|
169
|
+
if ! command -v systemctl &>/dev/null; then
|
|
170
|
+
echo "systemctl not found. Is systemd installed?" >&2
|
|
171
|
+
exit 1
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
mkdir -p "${workspace_dir}/bin" "${systemd_dir}" "${log_dir}" "$(dirname "${supervisor_pid_file}")"
|
|
175
|
+
|
|
176
|
+
# Create wrapper script
|
|
177
|
+
cat >"${wrapper_path}" <<EOF
|
|
178
|
+
#!/usr/bin/env bash
|
|
179
|
+
set -euo pipefail
|
|
180
|
+
export ACP_PROJECT_RUNTIME_HOME_DIR='${home_dir}'
|
|
181
|
+
export ACP_PROJECT_RUNTIME_SOURCE_HOME='${source_home}'
|
|
182
|
+
export ACP_PROJECT_RUNTIME_RUNTIME_HOME='${runtime_home}'
|
|
183
|
+
export ACP_PROJECT_RUNTIME_PROFILE_REGISTRY_ROOT='${profile_registry_root}'
|
|
184
|
+
export ACP_PROJECT_RUNTIME_PROFILE_ID='${profile_id}'
|
|
185
|
+
export ACP_PROJECT_RUNTIME_ENV_FILE='${env_file}'
|
|
186
|
+
export ACP_PROJECT_ID='${profile_id}'
|
|
187
|
+
export AGENT_PROJECT_ID='${profile_id}'
|
|
188
|
+
export ACP_PROJECT_RUNTIME_PATH='${base_path}'
|
|
189
|
+
export ACP_PROJECT_RUNTIME_SYNC_SCRIPT='${sync_script}'
|
|
190
|
+
export ACP_PROFILE_REGISTRY_ROOT='${profile_registry_root}'
|
|
191
|
+
EOF
|
|
192
|
+
|
|
193
|
+
if [[ -n "${coding_worker_override}" ]]; then
|
|
194
|
+
cat >>"${wrapper_path}" <<EOF
|
|
195
|
+
export ACP_CODING_WORKER='${coding_worker_override}'
|
|
196
|
+
EOF
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
cat >>"${wrapper_path}" <<EOF
|
|
200
|
+
exec bash '${supervisor_script}' --bootstrap-script '${bootstrap_script}' --pid-file '${supervisor_pid_file}' --delay-seconds '${delay_seconds}' --interval-seconds '${interval_seconds}'
|
|
201
|
+
EOF
|
|
202
|
+
chmod +x "${wrapper_path}"
|
|
203
|
+
|
|
204
|
+
# Create systemd unit file
|
|
205
|
+
cat >"${unit_file}" <<EOF
|
|
206
|
+
[Unit]
|
|
207
|
+
Description=Agent Control Plane - Project ${profile_id}
|
|
208
|
+
After=default.target
|
|
209
|
+
Wants=default.target
|
|
210
|
+
|
|
211
|
+
[Service]
|
|
212
|
+
Type=simple
|
|
213
|
+
ExecStart=${wrapper_path}
|
|
214
|
+
WorkingDirectory=${workspace_dir}
|
|
215
|
+
StandardOutput=append:${stdout_log}
|
|
216
|
+
StandardError=append:${stderr_log}
|
|
217
|
+
Restart=always
|
|
218
|
+
RestartSec=10
|
|
219
|
+
Environment=HOME=${home_dir}
|
|
220
|
+
Environment=PATH=${base_path}
|
|
221
|
+
Environment=ACP_PROFILE_REGISTRY_ROOT=${profile_registry_root}
|
|
222
|
+
|
|
223
|
+
[Install]
|
|
224
|
+
WantedBy=default.target
|
|
225
|
+
EOF
|
|
226
|
+
|
|
227
|
+
# Enable and start the service
|
|
228
|
+
if [[ "${ACP_PROJECT_RUNTIME_SKIP_SYSTEMCTL:-0}" == "1" ]]; then
|
|
229
|
+
printf 'SYSTEMD_INSTALL_STATUS=skipped-systemctl\n'
|
|
230
|
+
printf 'PROFILE_ID=%s\n' "${profile_id}"
|
|
231
|
+
printf 'UNIT_NAME=%s\n' "${unit_name}"
|
|
232
|
+
printf 'UNIT_FILE=%s\n' "${unit_file}"
|
|
233
|
+
printf 'WRAPPER=%s\n' "${wrapper_path}"
|
|
234
|
+
exit 0
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
# Enable user service (create symlink)
|
|
238
|
+
systemctl --user enable "${unit_name}" 2>&1 || true
|
|
239
|
+
|
|
240
|
+
# Start the service
|
|
241
|
+
systemctl --user restart "${unit_name}" 2>&1 || true
|
|
242
|
+
|
|
243
|
+
# Wait briefly for service to start
|
|
244
|
+
for _ in $(seq 1 10); do
|
|
245
|
+
if systemctl --user is-active --quiet "${unit_name}" 2>/dev/null; then
|
|
246
|
+
break
|
|
247
|
+
fi
|
|
248
|
+
sleep 1
|
|
249
|
+
done
|
|
250
|
+
|
|
251
|
+
printf 'SYSTEMD_INSTALL_STATUS=ok\n'
|
|
252
|
+
printf 'PROFILE_ID=%s\n' "${profile_id}"
|
|
253
|
+
printf 'UNIT_NAME=%s\n' "${unit_name}"
|
|
254
|
+
printf 'UNIT_FILE=%s\n' "${unit_file}"
|
|
255
|
+
printf 'WRAPPER=%s\n' "${wrapper_path}"
|
|
@@ -107,6 +107,9 @@ LAUNCHCTL_BIN="${ACP_PROJECT_RUNTIME_LAUNCHCTL_BIN:-$(command -v launchctl || tr
|
|
|
107
107
|
LAUNCH_AGENTS_DIR="${ACP_PROJECT_RUNTIME_LAUNCH_AGENTS_DIR:-${HOME}/Library/LaunchAgents}"
|
|
108
108
|
LAUNCHD_LABEL="${ACP_PROJECT_RUNTIME_LAUNCHD_LABEL:-ai.agent.project.${PROFILE_ID_SLUG}}"
|
|
109
109
|
LAUNCHD_PLIST="${ACP_PROJECT_RUNTIME_LAUNCHD_PLIST:-${LAUNCH_AGENTS_DIR}/${LAUNCHD_LABEL}.plist}"
|
|
110
|
+
SYSTEMCTL_BIN="${ACP_PROJECT_RUNTIME_SYSTEMCTL_BIN:-$(command -v systemctl || true)}"
|
|
111
|
+
SYSTEMD_DIR="${ACP_PROJECT_RUNTIME_SYSTEMD_DIR:-${HOME}/.config/systemd/user}"
|
|
112
|
+
SYSTEMD_UNIT_NAME="${ACP_PROJECT_RUNTIME_SYSTEMD_UNIT:-agent-project-${PROFILE_ID_SLUG}.service}"
|
|
110
113
|
SOURCE_HOME="${ACP_PROJECT_RUNTIME_SOURCE_HOME:-}"
|
|
111
114
|
RUNTIME_HOME="${ACP_PROJECT_RUNTIME_RUNTIME_HOME:-$(resolve_runtime_home)}"
|
|
112
115
|
SYNC_STAMP_FILE="${RUNTIME_HOME}/.agent-control-plane-runtime-sync.env"
|
|
@@ -385,6 +388,24 @@ launchd_service_state() {
|
|
|
385
388
|
fi
|
|
386
389
|
}
|
|
387
390
|
|
|
391
|
+
systemd_service_enabled_for_profile() {
|
|
392
|
+
[[ -n "${SYSTEMCTL_BIN}" && -x "${SYSTEMCTL_BIN}" ]] || return 1
|
|
393
|
+
[[ -f "${SYSTEMD_DIR}/${SYSTEMD_UNIT_NAME}" ]] || return 1
|
|
394
|
+
return 0
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
systemd_service_state() {
|
|
398
|
+
if ! systemd_service_enabled_for_profile; then
|
|
399
|
+
printf 'n/a\n'
|
|
400
|
+
return 0
|
|
401
|
+
fi
|
|
402
|
+
if "${SYSTEMCTL_BIN}" --user is-active --quiet "${SYSTEMD_UNIT_NAME}" 2>/dev/null; then
|
|
403
|
+
printf 'running\n'
|
|
404
|
+
else
|
|
405
|
+
printf 'stopped\n'
|
|
406
|
+
fi
|
|
407
|
+
}
|
|
408
|
+
|
|
388
409
|
print_status() {
|
|
389
410
|
local heartbeat=""
|
|
390
411
|
local shared_loop=""
|
|
@@ -439,6 +460,7 @@ print_status() {
|
|
|
439
460
|
fi
|
|
440
461
|
|
|
441
462
|
launchd_state="$(launchd_service_state)"
|
|
463
|
+
systemd_state="$(systemd_service_state)"
|
|
442
464
|
runtime_sync_status="$(sync_stamp_value "SYNC_STATUS" || true)"
|
|
443
465
|
runtime_sync_updated_at="$(sync_stamp_value "UPDATED_AT" || true)"
|
|
444
466
|
runtime_sync_fingerprint="$(sync_stamp_value "SOURCE_FINGERPRINT" || true)"
|
|
@@ -471,6 +493,7 @@ print_status() {
|
|
|
471
493
|
printf 'STATE_ROOT=%s\n' "${STATE_ROOT}"
|
|
472
494
|
printf 'RUNTIME_STATUS=%s\n' "${runtime_status}"
|
|
473
495
|
printf 'LAUNCHD_STATE=%s\n' "${launchd_state}"
|
|
496
|
+
printf 'SYSTEMD_STATE=%s\n' "${systemd_state}"
|
|
474
497
|
printf 'LAUNCHD_LABEL=%s\n' "${LAUNCHD_LABEL}"
|
|
475
498
|
printf 'LAUNCHD_PLIST=%s\n' "${LAUNCHD_PLIST}"
|
|
476
499
|
printf 'HEARTBEAT_PID=%s\n' "${heartbeat}"
|
|
@@ -587,6 +610,7 @@ stop_runtime() {
|
|
|
587
610
|
local session=""
|
|
588
611
|
local pid=""
|
|
589
612
|
local launchd_stopped="no"
|
|
613
|
+
local systemd_stopped="no"
|
|
590
614
|
|
|
591
615
|
while IFS= read -r session; do
|
|
592
616
|
[[ -n "${session}" ]] || continue
|
|
@@ -619,6 +643,11 @@ stop_runtime() {
|
|
|
619
643
|
launchd_stopped="yes"
|
|
620
644
|
fi
|
|
621
645
|
|
|
646
|
+
if systemd_service_enabled_for_profile; then
|
|
647
|
+
"${SYSTEMCTL_BIN}" --user stop "${SYSTEMD_UNIT_NAME}" >/dev/null 2>&1 || true
|
|
648
|
+
systemd_stopped="yes"
|
|
649
|
+
fi
|
|
650
|
+
|
|
622
651
|
if [[ -n "${TMUX_BIN}" ]]; then
|
|
623
652
|
for session in "${tmux_sessions[@]+"${tmux_sessions[@]}"}"; do
|
|
624
653
|
"${TMUX_BIN}" kill-session -t "${session}" >/dev/null 2>&1 || true
|
|
@@ -636,6 +665,7 @@ stop_runtime() {
|
|
|
636
665
|
printf 'ACTION=stop\n'
|
|
637
666
|
printf 'PROFILE_ID=%s\n' "${PROFILE_ID}"
|
|
638
667
|
printf 'LAUNCHD_STOPPED=%s\n' "${launchd_stopped}"
|
|
668
|
+
printf 'SYSTEMD_STOPPED=%s\n' "${systemd_stopped}"
|
|
639
669
|
printf 'STOPPED_PID_COUNT=%s\n' "$(printf '%s\n' "${pid_targets[@]+"${pid_targets[@]}"}" | awk 'NF {c+=1} END {print c+0}')"
|
|
640
670
|
printf 'STOPPED_TMUX_SESSION_COUNT=%s\n' "$(printf '%s\n' "${tmux_sessions[@]+"${tmux_sessions[@]}"}" | awk 'NF {c+=1} END {print c+0}')"
|
|
641
671
|
printf 'STOPPED_STALE_TMUX_SESSION_COUNT=%s\n' "$(printf '%s\n' "${stale_tmux_sessions[@]+"${stale_tmux_sessions[@]}"}" | awk 'NF {c+=1} END {print c+0}')"
|
|
@@ -679,6 +709,21 @@ start_runtime() {
|
|
|
679
709
|
return 0
|
|
680
710
|
fi
|
|
681
711
|
|
|
712
|
+
if systemd_service_enabled_for_profile; then
|
|
713
|
+
"${SYSTEMCTL_BIN}" --user restart "${SYSTEMD_UNIT_NAME}" >/dev/null 2>&1 || true
|
|
714
|
+
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
715
|
+
if "${SYSTEMCTL_BIN}" --user is-active --quiet "${SYSTEMD_UNIT_NAME}" 2>/dev/null; then
|
|
716
|
+
break
|
|
717
|
+
fi
|
|
718
|
+
sleep 1
|
|
719
|
+
done
|
|
720
|
+
printf 'ACTION=start\n'
|
|
721
|
+
printf 'PROFILE_ID=%s\n' "${PROFILE_ID}"
|
|
722
|
+
printf 'START_MODE=systemd\n'
|
|
723
|
+
printf 'SYSTEMD_UNIT=%s\n' "${SYSTEMD_UNIT_NAME}"
|
|
724
|
+
return 0
|
|
725
|
+
fi
|
|
726
|
+
|
|
682
727
|
kick_output="$(ACP_PROJECT_ID="${PROFILE_ID}" AGENT_PROJECT_ID="${PROFILE_ID}" bash "${KICK_SCRIPT}" "${delay_seconds}")"
|
|
683
728
|
if wait_for_runtime_start "${start_timeout}"; then
|
|
684
729
|
runtime_started_after_kick="1"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
flow_skill_dir="$(cd "${script_dir}/../.." && pwd)"
|
|
6
|
+
home_dir="${ACP_PROJECT_RUNTIME_HOME_DIR:-${HOME:-}}"
|
|
7
|
+
profile_registry_root="${ACP_PROJECT_RUNTIME_PROFILE_REGISTRY_ROOT:-${ACP_PROFILE_REGISTRY_ROOT:-${home_dir}/.agent-runtime/control-plane/profiles}}"
|
|
8
|
+
profile_id="${ACP_PROJECT_RUNTIME_PROFILE_ID:-${ACP_PROJECT_ID:-${AGENT_PROJECT_ID:-}}}"
|
|
9
|
+
env_file="${ACP_PROJECT_RUNTIME_ENV_FILE:-${profile_registry_root}/${profile_id}/runtime.env}"
|
|
10
|
+
|
|
11
|
+
if [[ -z "${home_dir}" ]]; then
|
|
12
|
+
echo "project systemd bootstrap requires HOME or ACP_PROJECT_RUNTIME_HOME_DIR" >&2
|
|
13
|
+
exit 64
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [[ -z "${profile_id}" ]]; then
|
|
17
|
+
echo "project systemd bootstrap requires ACP_PROJECT_RUNTIME_PROFILE_ID or ACP_PROJECT_ID" >&2
|
|
18
|
+
exit 64
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
export HOME="${home_dir}"
|
|
22
|
+
export ACP_PROFILE_REGISTRY_ROOT="${profile_registry_root}"
|
|
23
|
+
export ACP_PROJECT_ID="${profile_id}"
|
|
24
|
+
export AGENT_PROJECT_ID="${profile_id}"
|
|
25
|
+
|
|
26
|
+
if [[ -f "${env_file}" ]]; then
|
|
27
|
+
set -a
|
|
28
|
+
# shellcheck source=/dev/null
|
|
29
|
+
source "${env_file}"
|
|
30
|
+
set +a
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Resolve launch paths after runtime.env overrides are loaded so systemd can
|
|
34
|
+
# pin the project runtime to a source checkout or alternate runtime home.
|
|
35
|
+
source_home="${ACP_PROJECT_RUNTIME_SOURCE_HOME:-}"
|
|
36
|
+
runtime_home="${ACP_PROJECT_RUNTIME_RUNTIME_HOME:-${home_dir}/.agent-runtime/runtime-home}"
|
|
37
|
+
base_path="${ACP_PROJECT_RUNTIME_PATH:-/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin}"
|
|
38
|
+
sync_script="${ACP_PROJECT_RUNTIME_SYNC_SCRIPT:-${flow_skill_dir}/tools/bin/sync-shared-agent-home.sh}"
|
|
39
|
+
ensure_sync_script="${ACP_PROJECT_RUNTIME_ENSURE_SYNC_SCRIPT:-${flow_skill_dir}/tools/bin/ensure-runtime-sync.sh}"
|
|
40
|
+
runtime_heartbeat_script="${ACP_PROJECT_RUNTIME_HEARTBEAT_SCRIPT:-${runtime_home}/skills/openclaw/agent-control-plane/tools/bin/heartbeat-safe-auto.sh}"
|
|
41
|
+
always_sync="${ACP_PROJECT_RUNTIME_ALWAYS_SYNC:-0}"
|
|
42
|
+
export PATH="${base_path}"
|
|
43
|
+
|
|
44
|
+
if [[ ! -x "${ensure_sync_script}" && ! -x "${sync_script}" ]]; then
|
|
45
|
+
echo "project systemd bootstrap missing sync helper: ${ensure_sync_script}" >&2
|
|
46
|
+
exit 65
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if [[ -x "${ensure_sync_script}" ]]; then
|
|
50
|
+
ensure_args=(--runtime-home "${runtime_home}" --quiet)
|
|
51
|
+
if [[ -n "${source_home}" ]]; then
|
|
52
|
+
ensure_args=(--source-home "${source_home}" "${ensure_args[@]}")
|
|
53
|
+
fi
|
|
54
|
+
if [[ "${always_sync}" == "1" ]]; then
|
|
55
|
+
ensure_args=(--force "${ensure_args[@]}")
|
|
56
|
+
fi
|
|
57
|
+
if [[ "${flow_skill_dir}" == "${runtime_home}"/* ]]; then
|
|
58
|
+
printf 'RUNTIME_SYNC_SKIPPED=active-runtime-home\n'
|
|
59
|
+
else
|
|
60
|
+
bash "${ensure_sync_script}" "${ensure_args[@]}"
|
|
61
|
+
fi
|
|
62
|
+
elif [[ "${always_sync}" == "1" || ! -x "${runtime_heartbeat_script}" ]]; then
|
|
63
|
+
if [[ -z "${source_home}" ]]; then
|
|
64
|
+
source_home="${flow_skill_dir}"
|
|
65
|
+
fi
|
|
66
|
+
bash "${sync_script}" "${source_home}" "${runtime_home}" >/dev/null
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [[ ! -x "${runtime_heartbeat_script}" ]]; then
|
|
70
|
+
echo "project systemd bootstrap missing runtime heartbeat: ${runtime_heartbeat_script}" >&2
|
|
71
|
+
exit 66
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
exec bash "${runtime_heartbeat_script}"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'EOF'
|
|
6
|
+
Usage:
|
|
7
|
+
uninstall-project-systemd.sh --profile-id <id> [options]
|
|
8
|
+
|
|
9
|
+
Remove a previously installed systemd user service for an ACP project.
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--profile-id <id> Installed profile id to manage
|
|
13
|
+
--unit-name <name> Override systemd unit name (default: agent-project-<id>.service)
|
|
14
|
+
--help Show this help
|
|
15
|
+
EOF
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
# shellcheck source=/dev/null
|
|
20
|
+
source "${script_dir}/flow-config-lib.sh"
|
|
21
|
+
|
|
22
|
+
profile_id_override=""
|
|
23
|
+
unit_name_override=""
|
|
24
|
+
profile_registry_root_override="${ACP_PROJECT_RUNTIME_PROFILE_REGISTRY_ROOT:-${ACP_PROFILE_REGISTRY_ROOT:-}}"
|
|
25
|
+
|
|
26
|
+
while [[ $# -gt 0 ]]; do
|
|
27
|
+
case "$1" in
|
|
28
|
+
--profile-id) profile_id_override="${2:-}"; shift 2 ;;
|
|
29
|
+
--unit-name) unit_name_override="${2:-}"; shift 2 ;;
|
|
30
|
+
--help|-h) usage; exit 0 ;;
|
|
31
|
+
*) echo "Unknown argument: $1" >&2; usage >&2; exit 64 ;;
|
|
32
|
+
esac
|
|
33
|
+
done
|
|
34
|
+
|
|
35
|
+
if [[ -z "${profile_id_override}" ]]; then
|
|
36
|
+
usage >&2
|
|
37
|
+
exit 64
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
export ACP_PROJECT_ID="${profile_id_override}"
|
|
41
|
+
export AGENT_PROJECT_ID="${profile_id_override}"
|
|
42
|
+
|
|
43
|
+
if [[ -n "${profile_registry_root_override}" ]]; then
|
|
44
|
+
export ACP_PROFILE_REGISTRY_ROOT="${profile_registry_root_override}"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
flow_skill_dir="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
|
|
48
|
+
if ! flow_require_explicit_profile_selection "${flow_skill_dir}" "uninstall-project-systemd.sh"; then
|
|
49
|
+
exit 64
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
config_yaml="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
53
|
+
if [[ ! -f "${config_yaml}" ]]; then
|
|
54
|
+
printf 'profile not installed: %s\n' "${profile_id_override}" >&2
|
|
55
|
+
exit 66
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
profile_id="$(flow_resolve_adapter_id "${config_yaml}")"
|
|
59
|
+
profile_slug="$(printf '%s' "${profile_id}" | tr -c 'A-Za-z0-9._-' '-')"
|
|
60
|
+
home_dir="${ACP_PROJECT_RUNTIME_HOME_DIR:-${HOME:-}}"
|
|
61
|
+
systemd_dir="${ACP_PROJECT_RUNTIME_SYSTEMD_DIR:-${home_dir}/.config/systemd/user}"
|
|
62
|
+
unit_name="${unit_name_override:-${ACP_PROJECT_RUNTIME_SYSTEMD_UNIT:-agent-project-${profile_slug}.service}}"
|
|
63
|
+
unit_file="${systemd_dir}/${unit_name}"
|
|
64
|
+
workspace_dir="${ACP_PROJECT_RUNTIME_WORKSPACE_DIR:-${home_dir}/.agent-runtime/control-plane/workspace}"
|
|
65
|
+
wrapper_path="${workspace_dir}/bin/agent-project-${profile_slug}-systemd.sh"
|
|
66
|
+
|
|
67
|
+
# Check if systemd is available
|
|
68
|
+
if ! command -v systemctl &>/dev/null; then
|
|
69
|
+
echo "systemctl not found. Is systemd installed?" >&2
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Stop and disable the service
|
|
74
|
+
"${SYSTEMCTL_BIN}" --user stop "${unit_name}" 2>&1 || true
|
|
75
|
+
"${SYSTEMCTL_BIN}" --user disable "${unit_name}" 2>&1 || true
|
|
76
|
+
|
|
77
|
+
# Remove unit file
|
|
78
|
+
rm -f "${unit_file}"
|
|
79
|
+
|
|
80
|
+
# Remove wrapper script
|
|
81
|
+
rm -f "${wrapper_path}"
|
|
82
|
+
|
|
83
|
+
printf 'SYSTEMD_UNINSTALL_STATUS=ok\n'
|
|
84
|
+
printf 'PROFILE_ID=%s\n' "${profile_id}"
|
|
85
|
+
printf 'UNIT_NAME=%s\n' "${unit_name}"
|
|
86
|
+
printf 'UNIT_FILE=%s\n' "${unit_file}"
|
|
87
|
+
printf 'WRAPPER=%s\n' "${wrapper_path}"
|