Merge pull request #10246 from omacom/add-openclaw-ai-app

Add OpenClaw as a desktop app and a coding agent
This commit is contained in:
Spencer Bull authored and GitHub committed 2026-09-05 09:44:22 -05:00
commit eb56446c42
17 files changed
+1088 -6

No files matched your search

+65
View File
@@ -542,3 +542,68 @@ fi
grep -F "missing is not installed" "$test_tmp/missing-output" >/dev/null ||
fail "agent launcher explains when the default command is missing"
pass "agent launcher reports a missing default command"
# OpenClaw comes from its pacman package, not mise: choosing it must route
# through omarchy-install-openclaw-cli and never touch a mise environment.
cat >"$mock_bin/omarchy-pkg-present" <<'SH'
#!/bin/bash
[[ $1 == openclaw && ${OMARCHY_TEST_OPENCLAW_INSTALLED:-false} == "true" ]]
SH
cat >"$mock_bin/omarchy-pkg-add" <<'SH'
#!/bin/bash
printf '%s\n' "pkg-add $*" >>"$OMARCHY_TEST_STUB_LOG"
SH
cat >"$mock_bin/omarchy-launch-openclaw" <<'SH'
#!/bin/bash
printf '%s\0' omarchy-launch-openclaw "$@" >"$OMARCHY_TEST_AGENT_INLINE_LOG"
SH
cat >"$mock_bin/openclaw" <<'SH'
#!/bin/bash
exit 0
SH
chmod +x "$mock_bin/omarchy-pkg-present" "$mock_bin/omarchy-pkg-add" \
"$mock_bin/omarchy-launch-openclaw" "$mock_bin/openclaw"
: >"$launch_log"
: >"$terminal_log"
: >"$mise_history"
OMARCHY_TEST_OPENCLAW_INSTALLED=true omarchy-default-agent openclaw
read -r chosen <"$agent_file"
[[ $chosen == openclaw ]] || fail "choosing OpenClaw records it as the default agent"
mapfile -d '' -t launch_args <"$launch_log"
[[ ${launch_args[*]} == "--app-id=org.omarchy.agent omarchy-launch-openclaw --tui" ]] ||
fail "choosing OpenClaw launches its terminal UI"
[[ ! -s $terminal_log ]] || fail "an installed OpenClaw needs no install terminal"
! grep -q 'use -g openclaw' "$mise_history" || fail "OpenClaw never installs through mise"
pass "choosing OpenClaw uses the package and launches its terminal UI"
: >"$terminal_log"
OMARCHY_TEST_OPENCLAW_INSTALLED=false omarchy-default-agent openclaw
mapfile -d '' -t terminal_args <"$terminal_log"
[[ ${terminal_args[*]} == "omarchy-default-agent --install openclaw" ]] ||
fail "a missing OpenClaw routes through the install terminal"
pass "a missing OpenClaw routes through the install terminal"
: >"$stub_log"
: >"$inline_log"
OMARCHY_TEST_OPENCLAW_INSTALLED=false omarchy-default-agent --install openclaw >/dev/null
grep -Fx "pkg-add openclaw" "$stub_log" >/dev/null ||
fail "installing OpenClaw as default agent adds its package"
mapfile -d '' -t inline_args <"$inline_log"
[[ ${inline_args[*]} == "omarchy-launch-openclaw --tui" ]] ||
fail "installing OpenClaw as default agent hands over to its terminal UI"
pass "installing OpenClaw as default agent adds its package"
: >"$launch_log"
omarchy agent prompt "Review this project"
mapfile -d '' -t launch_args <"$launch_log"
# Element-wise: the prompt must travel as one argv entry, which a space-joined
# comparison could not tell apart from a prompt split into words.
[[ ${#launch_args[@]} == 5 &&
${launch_args[0]} == "--app-id=org.omarchy.agent" &&
${launch_args[1]} == "omarchy-launch-openclaw" &&
${launch_args[2]} == "--tui" &&
${launch_args[3]} == "--message" &&
${launch_args[4]} == "Review this project" ]] ||
fail "OpenClaw receives prompts through --message" "argv: ${launch_args[*]}"
pass "OpenClaw receives prompts through --message"
+189
View File
@@ -0,0 +1,189 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
mkdir -p "$tmp_dir/bin" "$tmp_dir/home"
export TEST_LOG="$tmp_dir/log"
export PATH="$tmp_dir/bin:$PATH"
export HOME="$tmp_dir/home"
for stub in omarchy-launch-webapp omarchy-launch-floating-terminal-with-presentation omarchy-openclaw-onboard; do
cat >"$tmp_dir/bin/$stub" <<SCRIPT
#!/bin/bash
printf '$stub:%s\n' "\$*" >>"\$TEST_LOG"
SCRIPT
chmod +x "$tmp_dir/bin/$stub"
done
# The retry loop sleeps between polls; a no-op keeps the suite fast.
printf '#!/bin/bash\n' >"$tmp_dir/bin/sleep"
chmod +x "$tmp_dir/bin/sleep"
# The launcher asks systemd whether the gateway unit is enabled; a flag file
# stands in for the user manager.
cat >"$tmp_dir/bin/systemctl" <<SCRIPT
#!/bin/bash
printf 'systemctl:%s\\n' "\$*" >>"\$TEST_LOG"
[[ \$* == *is-enabled* ]] && [[ -f $tmp_dir/gateway-enabled ]]
SCRIPT
chmod +x "$tmp_dir/bin/systemctl"
# A machine that never onboarded gets the wizard, not a dashboard probe.
"$ROOT/bin/omarchy-launch-openclaw"
# Through omarchy-openclaw-onboard, never bare `openclaw onboard`, which as of
# 2026.9.1 ends in a browser tab with a foreground gateway and never returns.
grep -q '^omarchy-launch-floating-terminal-with-presentation:omarchy-openclaw-onboard && \[\[ -f \$HOME/.openclaw/openclaw.json \]\] && omarchy-launch-openclaw$' "$TEST_LOG" ||
fail "OpenClaw launch hands a never-onboarded machine to the wizard" "$(grep floating "$TEST_LOG")"
! grep -q '^omarchy-launch-webapp:' "$TEST_LOG" ||
fail "OpenClaw launch hands a never-onboarded machine to the wizard" "webapp opened anyway"
pass "OpenClaw launch hands a never-onboarded machine to the wizard"
mkdir -p "$HOME/.openclaw"
touch "$HOME/.openclaw/openclaw.json"
# A running gateway answers the first probe; its handoff URL opens as the app.
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
[[ $* == *--json* ]] &&
echo '{"ok":true,"url":"http://127.0.0.1:18789/?token=shared","browserUrl":"http://127.0.0.1:18789/#handoff"}'
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
"$ROOT/bin/omarchy-launch-openclaw"
grep -q '^omarchy-launch-webapp:http://127.0.0.1:18789/#handoff$' "$TEST_LOG" ||
fail "OpenClaw launch opens the running gateway's handoff URL"
pass "OpenClaw launch opens the running gateway's handoff URL"
# A machine whose gateway unit was never enabled (never installed, or an
# install that died after writing the unit) gets `gateway install --force`,
# then is polled until the dashboard answers. Never `dashboard --yes`: since
# 2026.9.1 that neither installs nor starts anything and, once the gateway is
# up, pushes a one-time pairing URL into the clipboard.
cat >"$tmp_dir/bin/openclaw" <<SCRIPT
#!/bin/bash
printf 'openclaw:%s\\n' "\$*" >>"\$TEST_LOG"
if [[ \$* == *--json* ]]; then
[[ -f $tmp_dir/gateway-up ]] || { echo '{"ok":false,"reason":"Gateway is not running."}'; exit 1; }
echo '{"ok":true,"browserUrl":"http://127.0.0.1:18789/#cold-start"}'
elif [[ \$1 == gateway ]]; then
touch "$tmp_dir/gateway-up"
fi
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
rm -f "$tmp_dir/gateway-up" "$tmp_dir/gateway-enabled"
mkdir -p "$HOME/.config/systemd/user"
touch "$HOME/.config/systemd/user/openclaw-gateway.service"
: >"$TEST_LOG"
"$ROOT/bin/omarchy-launch-openclaw"
grep -q '^openclaw:gateway install --force$' "$TEST_LOG" ||
fail "OpenClaw launch installs a never-enabled gateway before opening the app"
! grep -q '^openclaw:gateway start$' "$TEST_LOG" ||
fail "OpenClaw launch installs a never-enabled gateway before opening the app" "started the half-installed unit instead"
grep -q '^omarchy-launch-webapp:http://127.0.0.1:18789/#cold-start$' "$TEST_LOG" ||
fail "OpenClaw launch installs a never-enabled gateway before opening the app" "webapp never opened"
pass "OpenClaw launch installs a never-enabled gateway before opening the app"
# An enabled but stopped unit is started, not reinstalled.
touch "$tmp_dir/gateway-enabled"
rm -f "$tmp_dir/gateway-up"
: >"$TEST_LOG"
"$ROOT/bin/omarchy-launch-openclaw"
grep -q '^openclaw:gateway start$' "$TEST_LOG" ||
fail "OpenClaw launch starts a stopped gateway before opening the app"
! grep -q '^openclaw:gateway install' "$TEST_LOG" ||
fail "OpenClaw launch starts a stopped gateway before opening the app" "reinstalled the unit"
! grep -q -- '--yes' "$TEST_LOG" ||
fail "OpenClaw launch starts a stopped gateway before opening the app" "fell back to dashboard --yes"
grep -q '^omarchy-launch-webapp:http://127.0.0.1:18789/#cold-start$' "$TEST_LOG" ||
fail "OpenClaw launch starts a stopped gateway before opening the app" "webapp never opened"
pass "OpenClaw launch starts a stopped gateway before opening the app"
rm -f "$tmp_dir/gateway-enabled"
# A dashboard probe that hangs is cut off, so the launch still fails cleanly
# instead of sitting forever behind an app-grid icon with no terminal.
cat >"$tmp_dir/bin/timeout" <<SCRIPT
#!/bin/bash
printf 'timeout:%s\\n' "\$1" >>"\$TEST_LOG"
exit 124
SCRIPT
chmod +x "$tmp_dir/bin/timeout"
: >"$TEST_LOG"
rc=0
"$ROOT/bin/omarchy-launch-openclaw" >/dev/null 2>&1 || rc=$?
[[ $rc != 0 ]] || fail "OpenClaw launch bounds a hanging dashboard probe"
grep -q '^timeout:10$' "$TEST_LOG" || fail "OpenClaw launch bounds a hanging dashboard probe" "probe ran without a timeout"
pass "OpenClaw launch bounds a hanging dashboard probe"
rm -f "$tmp_dir/bin/timeout"
# A gateway that never answers fails the launch instead of opening a dead page.
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
[[ $* == *--json* ]] && exit 1
exit 0
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
webapp_calls_before=$(grep -c '^omarchy-launch-webapp:' "$TEST_LOG" || true)
rc=0
"$ROOT/bin/omarchy-launch-openclaw" >/dev/null 2>&1 || rc=$?
webapp_calls_after=$(grep -c '^omarchy-launch-webapp:' "$TEST_LOG" || true)
[[ $rc != 0 ]] || fail "OpenClaw launch fails cleanly when the gateway never comes up"
[[ $webapp_calls_before == "$webapp_calls_after" ]] ||
fail "OpenClaw launch fails cleanly when the gateway never comes up" "webapp opened anyway"
pass "OpenClaw launch fails cleanly when the gateway never comes up"
# --tui runs onboarding in the terminal it is already in, then attaches to the
# gateway with `openclaw tui` -- never the embedded chat, which the running
# gateway's state-directory lock would refuse.
rm -f "$HOME/.openclaw/openclaw.json"
cat >"$tmp_dir/bin/omarchy-openclaw-onboard" <<SCRIPT
#!/bin/bash
printf 'omarchy-openclaw-onboard:%s\n' "\$*" >>"\$TEST_LOG"
mkdir -p "\$HOME/.openclaw" && touch "\$HOME/.openclaw/openclaw.json"
SCRIPT
chmod +x "$tmp_dir/bin/omarchy-openclaw-onboard"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
[[ $* == *--json* ]] && echo '{"ok":true,"browserUrl":"http://127.0.0.1:18789/#tui"}'
exit 0
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
floating_calls_before=$(grep -c '^omarchy-launch-floating-terminal-with-presentation:' "$TEST_LOG" || true)
"$ROOT/bin/omarchy-launch-openclaw" --tui
grep -q '^omarchy-openclaw-onboard:$' "$TEST_LOG" ||
fail "--tui onboards in the current terminal" "$(grep 'onboard' "$TEST_LOG")"
floating_calls_after=$(grep -c '^omarchy-launch-floating-terminal-with-presentation:' "$TEST_LOG" || true)
[[ $floating_calls_before == "$floating_calls_after" ]] ||
fail "--tui onboards in the current terminal" "spawned a floating terminal"
grep -q '^openclaw:tui$' "$TEST_LOG" || fail "--tui attaches to the gateway"
pass "--tui onboards in place and attaches to the gateway"
# The stub also logs argv one entry per line, so a prompt split into words
# would show up as extra lines rather than pass a space-joined comparison.
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
[[ $1 == tui ]] && printf 'argv:[%s]\n' "$@" >>"$TEST_LOG"
[[ $* == *--json* ]] && echo '{"ok":true,"browserUrl":"http://127.0.0.1:18789/#tui"}'
exit 0
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
: >"$TEST_LOG"
"$ROOT/bin/omarchy-launch-openclaw" --tui --message "Review this project"
[[ $(grep -c '^argv:' "$TEST_LOG") == 3 ]] && grep -q '^argv:\[Review this project\]$' "$TEST_LOG" ||
fail "--tui seeds the session through --message" "$(grep '^argv:' "$TEST_LOG" | tr '\n' ' ')"
! grep -q '^omarchy-launch-webapp:' "$TEST_LOG" ||
fail "--tui seeds the session through --message" "webapp opened instead"
pass "--tui seeds the session through --message"
+3 -2
View File
@@ -225,6 +225,7 @@ const expectedAgents = {
codex: { icon: '\ue905', iconFont: 'omarchy', label: 'Codex' },
grok: { icon: '\ue904', iconFont: 'omarchy', label: 'Grok' },
hermes: { icon: '\ue90a', iconFont: 'omarchy', label: 'Hermes' },
openclaw: { icon: '\ue90c', iconFont: 'omarchy', label: 'OpenClaw' },
copilot: { icon: '', label: 'Copilot' },
crush: { icon: '󰋑', label: 'Crush' },
}
@@ -245,7 +246,7 @@ assertDeepEqual(
defaultItems
.filter(item => item.parent === 'setup.default.agent')
.map(item => item.label),
['Antigravity', 'Claude', 'Codex', 'Copilot', 'Crush', 'Grok', 'Hermes', 'omp', 'OpenCode', 'Ori', 'Pi'],
['Antigravity', 'Claude', 'Codex', 'Copilot', 'Crush', 'Grok', 'Hermes', 'omp', 'OpenClaw', 'OpenCode', 'Ori', 'Pi'],
'menu sorts coding agents alphabetically'
)
const expectedDefaults = {
@@ -637,5 +638,5 @@ assert(
JS
font_charset=$(fc-query --format='%{charset}' "$ROOT/default/fonts/omarchy/omarchy.ttf")
[[ $font_charset == *"e900-e90a"* ]] || fail "Omarchy icon font includes every custom menu glyph"
[[ $font_charset == *"e900-e90a"* && $font_charset == *"e90c"* ]] || fail "Omarchy icon font includes every custom menu glyph"
pass "Omarchy icon font includes the official agent marks"
+288
View File
@@ -0,0 +1,288 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
mkdir -p "$tmp_dir/bin" "$tmp_dir/home"
export TEST_LOG="$tmp_dir/log"
export PATH="$tmp_dir/bin:$PATH"
export HOME="$tmp_dir/home"
export OMARCHY_OPENCLAW_ONBOARD_SETTLE_SECONDS=0
unit="$HOME/.config/systemd/user/openclaw-gateway.service"
export unit
# systemd and ss stand in for themselves. Once the wizard has written the unit
# it is running as pid 4242, and that same process holds the gateway port
# unless a test plants a different listener (an orphan) in $listener_file.
listener_file="$tmp_dir/listener"
export listener_file
cat >"$tmp_dir/bin/systemctl" <<'SCRIPT'
#!/bin/bash
printf 'systemctl:%s\n' "$*" >>"$TEST_LOG"
if [[ $* == *MainPID* ]]; then
[[ -f $unit ]] && echo 4242 || echo 0
exit 0
fi
[[ $* == *is-active* ]] && [[ -f $unit ]]
SCRIPT
chmod +x "$tmp_dir/bin/systemctl"
cat >"$tmp_dir/bin/ss" <<'SCRIPT'
#!/bin/bash
pid=
[[ -f $listener_file ]] && pid=$(cat "$listener_file")
[[ -z $pid && -f $unit ]] && pid=4242
[[ -n $pid ]] && echo "LISTEN 0 511 127.0.0.1:18789 0.0.0.0:* users:((\"node\",pid=$pid,fd=3))"
exit 0
SCRIPT
chmod +x "$tmp_dir/bin/ss"
export OMARCHY_OPENCLAW_ONBOARD_GATEWAY_TIMEOUT=1
# A wizard that configures OpenClaw, prints its outro, and then lingers forever
# on an open handle: the 2026.9.1 --skip-ui behaviour. It "installs the
# gateway" by writing the config a moment in, so the gateway only starts
# answering partway through, never at once.
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
case $1 in
onboard)
( sleep 1; mkdir -p "$HOME/.openclaw" "${unit%/*}"; touch "$HOME/.openclaw/openclaw.json" "$unit" ) &
trap 'echo terminated >>"$TEST_LOG"; exit 143' TERM
while :; do sleep 0.2; done
;;
dashboard)
[[ -f $HOME/.openclaw/openclaw.json ]] && echo '{"ok":true,"port":18789}' || { echo '{"ok":false}'; exit 1; }
;;
esac
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
start=$SECONDS
rc=0
"$ROOT/bin/omarchy-openclaw-onboard" </dev/null >/dev/null 2>&1 || rc=$?
elapsed=$((SECONDS - start))
grep -q '^openclaw:onboard --flow quickstart --install-daemon --skip-ui$' "$TEST_LOG" ||
fail "onboarding runs the classic quickstart wizard with the service install" "$(grep '^openclaw:onboard' "$TEST_LOG" || true)"
pass "onboarding runs the classic quickstart wizard with the service install"
[[ $rc == 0 ]] || fail "a wizard that lingers after the gateway is up is stopped and counts as success" "rc=$rc"
grep -q '^terminated$' "$TEST_LOG" ||
fail "a wizard that lingers after the gateway is up is stopped and counts as success" "wizard was never signalled"
(( elapsed < 30 )) || fail "a wizard that lingers after the gateway is up is stopped and counts as success" "took ${elapsed}s"
pass "a wizard that lingers after the gateway is up is stopped and counts as success"
# The wizard only starts being stopped once the gateway actually answers: a
# stub that never writes the config is left alone and must be ended by its own
# exit, not the watcher.
: >"$TEST_LOG"
rm -rf "$HOME/.openclaw" "$unit" "$listener_file"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
[[ $1 == onboard ]] && { echo "Skipped for now."; exit 3; }
echo '{"ok":false}'; exit 1
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
rc=0
"$ROOT/bin/omarchy-openclaw-onboard" </dev/null >/dev/null 2>&1 || rc=$?
[[ $rc == 3 ]] || fail "an abandoned wizard passes its exit code through" "rc=$rc"
[[ ! -f $HOME/.openclaw/openclaw.json ]] || fail "an abandoned wizard passes its exit code through" "config appeared"
! grep -q '^terminated$' "$TEST_LOG" || fail "an abandoned wizard passes its exit code through" "watcher signalled it anyway"
pass "an abandoned wizard passes its exit code through"
# A wizard that finishes and exits on its own is simply waited for.
: >"$TEST_LOG"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
[[ $1 == onboard ]] && { mkdir -p "$HOME/.openclaw"; touch "$HOME/.openclaw/openclaw.json"; exit 0; }
echo '{"ok":true,"port":18789}'
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
rc=0
"$ROOT/bin/omarchy-openclaw-onboard" </dev/null >/dev/null 2>&1 || rc=$?
[[ $rc == 0 ]] || fail "a wizard that exits cleanly is waited for" "rc=$rc"
pass "a wizard that exits cleanly is waited for"
# Input reaches the wizard: it runs in the foreground, not backgrounded (which
# would stop it on SIGTTIN the moment it read stdin).
: >"$TEST_LOG"
rm -rf "$HOME/.openclaw" "$unit" "$listener_file"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
case $1 in
onboard)
read -r answer
printf 'answer:%s\n' "$answer" >>"$TEST_LOG"
mkdir -p "$HOME/.openclaw"; touch "$HOME/.openclaw/openclaw.json"
exit 0
;;
dashboard) echo '{"ok":true,"port":18789}' ;;
esac
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
printf 'gpt-5\n' | "$ROOT/bin/omarchy-openclaw-onboard" >/dev/null 2>&1
grep -q '^answer:gpt-5$' "$TEST_LOG" ||
fail "the wizard reads terminal input" "$(grep '^answer' "$TEST_LOG" || echo 'no input reached it')"
pass "the wizard reads terminal input"
# An already-configured machine whose gateway answers is not onboarded again:
# the wizard's repair pass would otherwise be stopped mid-prompt the moment
# the watcher saw the pre-existing gateway.
: >"$TEST_LOG"
mkdir -p "$HOME/.openclaw" && touch "$HOME/.openclaw/openclaw.json"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
[[ $1 == onboard ]] && { echo wizard-ran >>"$TEST_LOG"; exit 0; }
echo '{"ok":true,"port":18789}'
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
rc=0
"$ROOT/bin/omarchy-openclaw-onboard" </dev/null >/dev/null 2>&1 || rc=$?
[[ $rc == 0 ]] || fail "an already-running OpenClaw is left alone" "rc=$rc"
! grep -q '^wizard-ran$' "$TEST_LOG" || fail "an already-running OpenClaw is left alone" "wizard ran anyway"
pass "an already-running OpenClaw is left alone"
# Setup applied but the gateway never answers: the wizard would linger
# forever, so it is stopped after the deadline and that is reported as failure.
: >"$TEST_LOG"
rm -rf "$HOME/.openclaw" "$unit" "$listener_file"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
case $1 in
onboard)
mkdir -p "$HOME/.openclaw"; touch "$HOME/.openclaw/openclaw.json"
trap 'echo terminated >>"$TEST_LOG"; exit 143' TERM
while :; do sleep 0.2; done
;;
dashboard) echo '{"ok":false}'; exit 1 ;;
esac
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
start=$SECONDS
rc=0
err=$("$ROOT/bin/omarchy-openclaw-onboard" </dev/null 2>&1 >/dev/null) || rc=$?
elapsed=$((SECONDS - start))
[[ $rc == 1 ]] || fail "a gateway that never comes up ends the wait with a failure" "rc=$rc"
grep -q '^terminated$' "$TEST_LOG" || fail "a gateway that never comes up ends the wait with a failure" "wizard left running"
[[ $err == *"gateway status"* ]] || fail "a gateway that never comes up ends the wait with a failure" "no diagnostic: $err"
(( elapsed < 30 )) || fail "a gateway that never comes up ends the wait with a failure" "took ${elapsed}s"
pass "a gateway that never comes up ends the wait with a failure"
# A signal at this script alone takes the wizard down with it.
: >"$TEST_LOG"
rm -rf "$HOME/.openclaw" "$unit" "$listener_file"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
case $1 in
onboard) trap 'echo terminated >>"$TEST_LOG"; exit 143' TERM; while :; do sleep 0.2; done ;;
dashboard) echo '{"ok":false}'; exit 1 ;;
esac
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
"$ROOT/bin/omarchy-openclaw-onboard" </dev/null >/dev/null 2>&1 &
wrapper=$!
sleep 1
kill -TERM "$wrapper"
wait "$wrapper" 2>/dev/null || true
sleep 1
grep -q '^terminated$' "$TEST_LOG" || fail "stopping the wrapper stops the wizard" "wizard survived"
! pgrep -f "$tmp_dir/bin/openclaw onboard" >/dev/null || fail "stopping the wrapper stops the wizard" "wizard process still alive"
pass "stopping the wrapper stops the wizard"
# A config left by an earlier, incomplete setup does not start the gateway
# deadline: only this run applying setup does. The wizard here takes longer
# than the deadline at its prompts, never touches the old config, and must be
# left to finish on its own.
: >"$TEST_LOG"
mkdir -p "$HOME/.openclaw" && touch "$HOME/.openclaw/openclaw.json"
sleep 1
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
case $1 in
onboard) trap 'echo terminated >>"$TEST_LOG"; exit 143' TERM; sleep 4; echo finished >>"$TEST_LOG"; exit 0 ;;
dashboard) echo '{"ok":false}'; exit 1 ;;
esac
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
rc=0
"$ROOT/bin/omarchy-openclaw-onboard" </dev/null >/dev/null 2>&1 || rc=$?
[[ $rc == 0 ]] || fail "a stale config does not start the gateway deadline" "rc=$rc"
grep -q '^finished$' "$TEST_LOG" || fail "a stale config does not start the gateway deadline" "wizard was cut short"
! grep -q '^terminated$' "$TEST_LOG" || fail "a stale config does not start the gateway deadline" "wizard was signalled"
pass "a stale config does not start the gateway deadline"
# ...while the same stale config being rewritten by this run does arm it.
: >"$TEST_LOG"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
case $1 in
onboard) touch "$HOME/.openclaw/openclaw.json"; trap 'echo terminated >>"$TEST_LOG"; exit 143' TERM; while :; do sleep 0.2; done ;;
dashboard) echo '{"ok":false}'; exit 1 ;;
esac
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
rc=0
"$ROOT/bin/omarchy-openclaw-onboard" </dev/null >/dev/null 2>&1 || rc=$?
[[ $rc == 1 ]] || fail "a config rewritten by this run arms the gateway deadline" "rc=$rc"
grep -q '^terminated$' "$TEST_LOG" || fail "a config rewritten by this run arms the gateway deadline" "wizard left running"
pass "a config rewritten by this run arms the gateway deadline"
# A gateway that answers while no config exists is not this run's: with no
# config `openclaw dashboard --json` still probes the default port, so a
# process left behind by something else (an earlier guided onboarding's
# foreground gateway) would otherwise end a wizard still at its first prompt.
: >"$TEST_LOG"
rm -rf "$HOME/.openclaw" "$unit" "$listener_file"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
case $1 in
onboard) trap 'echo terminated >>"$TEST_LOG"; exit 143' TERM; sleep 4; echo finished >>"$TEST_LOG"; exit 0 ;;
dashboard) echo '{"ok":true,"port":18789}' ;;
esac
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
rc=0
"$ROOT/bin/omarchy-openclaw-onboard" </dev/null >/dev/null 2>&1 || rc=$?
[[ $rc == 0 ]] || fail "an orphaned gateway answering with no config does not end the wizard" "rc=$rc"
grep -q '^finished$' "$TEST_LOG" || fail "an orphaned gateway answering with no config does not end the wizard" "wizard was cut short"
! grep -q '^terminated$' "$TEST_LOG" || fail "an orphaned gateway answering with no config does not end the wizard" "wizard was signalled"
pass "an orphaned gateway answering with no config does not end the wizard"
# And after setup, an orphan still bound to the port is not success even
# while the unit reports active: Type=simple counts it active from the fork,
# before it has discovered the port is taken. The port's owner has to be the
# unit's own main process. Here it never is, so the deadline ends the wait as
# a failure rather than opening the app on the orphan.
: >"$TEST_LOG"
rm -rf "$HOME/.openclaw" "$unit" "$listener_file"
echo 999 >"$listener_file"
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
case $1 in
onboard) mkdir -p "$HOME/.openclaw" "${unit%/*}"; touch "$HOME/.openclaw/openclaw.json" "$unit"; trap 'echo terminated >>"$TEST_LOG"; exit 143' TERM; while :; do sleep 0.2; done ;;
dashboard) echo '{"ok":true,"port":18789}' ;;
esac
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
rc=0
"$ROOT/bin/omarchy-openclaw-onboard" </dev/null >/dev/null 2>&1 || rc=$?
[[ $rc == 1 ]] || fail "an orphan holding the port is not mistaken for the active unit" "rc=$rc"
grep -q '^terminated$' "$TEST_LOG" || fail "an orphan holding the port is not mistaken for the active unit" "wizard left running"
grep -q '^systemctl:--user show -p MainPID --value openclaw-gateway.service$' "$TEST_LOG" ||
fail "an orphan holding the port is not mistaken for the active unit" "unit ownership never checked"
pass "an orphan holding the port is not mistaken for the active unit"
rm -f "$listener_file"
+158
View File
@@ -93,3 +93,161 @@ ollama_row=$(grep '^ "remove.ai.ollama":' "$ROOT/default/omarchy/omarchy-menu.j
[[ $ollama_row == *'"when":"omarchy-pkg-present ollama"'* ]] ||
fail "Ollama removal is offered only where the package is installed" "$ollama_row"
pass "Ollama removal is offered only where the package is installed"
# OpenClaw's gateway unit and web app launcher are the app's own; the agent
# state in ~/.openclaw is the user's. systemctl and openclaw are stubbed so the
# sandbox never reaches the real user manager or a real gateway.
cat >"$tmp_dir/bin/systemctl" <<'SCRIPT'
#!/bin/bash
printf 'systemctl:%s\n' "$*" >>"$TEST_LOG"
SCRIPT
chmod +x "$tmp_dir/bin/systemctl"
# Default openclaw stub: the packaged CLI predates `gateway uninstall`, so the
# remover has to fall back to its manual systemd path.
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
exit 1
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
fresh_openclaw_home() {
fresh_home
mkdir -p "$HOME/.config/systemd/user/default.target.wants" "$HOME/.openclaw" \
"$HOME/.local/share/applications" "$HOME/.local/share/icons/hicolor/256x256/apps"
touch "$HOME/.config/systemd/user/openclaw-gateway.service" \
"$HOME/.config/systemd/user/openclaw-gateway.service.bak" \
"$HOME/.config/systemd/user/openclaw-node.service" \
"$HOME/.openclaw/openclaw.json" \
"$HOME/.local/share/applications/OpenClaw.desktop" \
"$HOME/.local/share/icons/hicolor/256x256/apps/openclaw.png"
ln -s ../openclaw-gateway.service \
"$HOME/.config/systemd/user/default.target.wants/openclaw-gateway.service"
ln -s ../openclaw-node.service \
"$HOME/.config/systemd/user/default.target.wants/openclaw-node.service"
}
# gum would ask about ~/.openclaw; the tests never run on a terminal, so it
# must not even be reached. Logging it proves that.
cat >"$tmp_dir/bin/gum" <<'SCRIPT'
#!/bin/bash
printf 'gum:%s\n' "$*" >>"$TEST_LOG"
exit 0
SCRIPT
chmod +x "$tmp_dir/bin/gum"
fresh_openclaw_home
"$ROOT/bin/omarchy-remove-ai-openclaw" >/dev/null
for gone in .config/systemd/user/openclaw-gateway.service \
.config/systemd/user/openclaw-gateway.service.bak \
.config/systemd/user/default.target.wants/openclaw-gateway.service \
.config/systemd/user/openclaw-node.service \
.config/systemd/user/default.target.wants/openclaw-node.service \
.local/share/applications/OpenClaw.desktop \
.local/share/icons/hicolor/256x256/apps/openclaw.png; do
[[ ! -e $HOME/$gone && ! -L $HOME/$gone ]] || fail "OpenClaw removal deletes the service and launcher it installed" "$gone"
done
pass "OpenClaw removal deletes the service and launcher it installed"
grep -q '^systemctl:--user disable --now openclaw-gateway.service$' "$TEST_LOG" ||
fail "OpenClaw removal stops the gateway service"
grep -q '^systemctl:--user disable --now openclaw-node.service$' "$TEST_LOG" ||
fail "OpenClaw removal stops the gateway service" "node host service left running"
for unit in openclaw-gateway.service openclaw-node.service; do
grep -q "^systemctl:--user reset-failed $unit\$" "$TEST_LOG" ||
fail "OpenClaw removal stops the gateway service" "$unit left in systemd's failed list"
done
pass "OpenClaw removal stops the gateway service"
# Without a terminal there is nobody to ask, so the state stays and gum is
# never invoked (a gum that answered "yes" on its own would be a data loss).
[[ -f $HOME/.openclaw/openclaw.json ]] || fail "OpenClaw removal keeps the user's agent state"
! grep -q '^gum:' "$TEST_LOG" ||
fail "OpenClaw removal keeps the user's agent state" "asked about ~/.openclaw without a terminal"
pass "OpenClaw removal keeps the user's agent state"
# A CLI that knows `gateway uninstall` owns the teardown; the manual systemd
# fallback must not run.
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
rm -f "$HOME/.config/systemd/user/openclaw-$1.service" \
"$HOME/.config/systemd/user/default.target.wants/openclaw-$1.service"
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
: >"$TEST_LOG"
fresh_openclaw_home
"$ROOT/bin/omarchy-remove-ai-openclaw" >/dev/null
grep -q '^openclaw:gateway uninstall$' "$TEST_LOG" ||
fail "OpenClaw removal prefers upstream's own gateway teardown"
grep -q '^openclaw:node uninstall$' "$TEST_LOG" ||
fail "OpenClaw removal prefers upstream's own gateway teardown" "node host not handed to upstream"
! grep -q '^systemctl:--user disable' "$TEST_LOG" ||
fail "OpenClaw removal prefers upstream's own gateway teardown" "manual disable ran too"
pass "OpenClaw removal prefers upstream's own gateway teardown"
# Without the unit file there is nothing of ours registered, so systemd stays untouched.
systemctl_calls_before=$(grep -c '^systemctl:' "$TEST_LOG" || true)
fresh_home
"$ROOT/bin/omarchy-remove-ai-openclaw" >/dev/null
systemctl_calls_after=$(grep -c '^systemctl:' "$TEST_LOG" || true)
[[ $systemctl_calls_before == "$systemctl_calls_after" ]] ||
fail "OpenClaw removal leaves systemd alone when onboarding never ran"
pass "OpenClaw removal leaves systemd alone when onboarding never ran"
# A gateway that will not stop aborts the removal before the package drop:
# pacman would otherwise strand the live process on deleted code.
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'
#!/bin/bash
printf 'openclaw:%s\n' "$*" >>"$TEST_LOG"
exit 1
SCRIPT
chmod +x "$tmp_dir/bin/openclaw"
cat >"$tmp_dir/bin/systemctl" <<'SCRIPT'
#!/bin/bash
printf 'systemctl:%s\n' "$*" >>"$TEST_LOG"
[[ $2 == disable ]] && exit 1
[[ $2 == is-active ]] && exit 0
exit 0
SCRIPT
chmod +x "$tmp_dir/bin/systemctl"
drop_calls_before=$(grep -c '^drop:openclaw$' "$TEST_LOG" || true)
fresh_openclaw_home
rc=0
"$ROOT/bin/omarchy-remove-ai-openclaw" >/dev/null 2>&1 || rc=$?
drop_calls_after=$(grep -c '^drop:openclaw$' "$TEST_LOG" || true)
[[ $rc != 0 ]] || fail "OpenClaw removal aborts when the gateway cannot be stopped"
[[ -f $HOME/.config/systemd/user/openclaw-gateway.service ]] ||
fail "OpenClaw removal aborts when the gateway cannot be stopped" "unit file deleted"
[[ $drop_calls_before == "$drop_calls_after" ]] ||
fail "OpenClaw removal aborts when the gateway cannot be stopped" "package dropped anyway"
pass "OpenClaw removal aborts when the gateway cannot be stopped"
# An unreachable user manager is not a stopped gateway: every probe failing
# with no answer must still abort, not read as "confirmed inactive".
cat >"$tmp_dir/bin/systemctl" <<'SCRIPT'
#!/bin/bash
printf 'systemctl:%s\n' "$*" >>"$TEST_LOG"
echo "Failed to connect to user scope bus" >&2
exit 1
SCRIPT
chmod +x "$tmp_dir/bin/systemctl"
: >"$TEST_LOG"
fresh_openclaw_home
rc=0
"$ROOT/bin/omarchy-remove-ai-openclaw" >/dev/null 2>&1 || rc=$?
[[ $rc != 0 ]] || fail "OpenClaw removal aborts when systemd cannot be reached"
[[ -f $HOME/.config/systemd/user/openclaw-gateway.service ]] ||
fail "OpenClaw removal aborts when systemd cannot be reached" "unit file deleted"
! grep -q '^drop:openclaw$' "$TEST_LOG" ||
fail "OpenClaw removal aborts when systemd cannot be reached" "package dropped anyway"
pass "OpenClaw removal aborts when systemd cannot be reached"