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
19 changes: 18 additions & 1 deletion module/config/service.nix
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ let

clawdeServiceRestartCommand =
if pkgs.stdenv.hostPlatform.isLinux then
"systemctl --user restart clawde"
"systemctl --user daemon-reload && systemctl --user restart clawde"
else
"launchctl kickstart -k gui/UID/org.nix-community.home.clawde";

Expand DownExpand Up@@ -55,6 +55,16 @@ let
"${clawdeServiceSpecificationFile}"
];

clawdeServiceDeployedCommandFile = pkgs.writeText "clawde-service-deployed-command" (
lib.concatStringsSep " " clawdeServiceExecArguments
);

clawdeSupervisorRefresh = pkgs.writeShellScriptBin "clawde-supervisor-refresh" ''
exec ${pkgs.python312}/bin/python3 ${../scripts/clawde-supervisor-refresh.py} \
--deployed-command-file ${clawdeServiceDeployedCommandFile} \
--restart-command ${lib.escapeShellArg clawdeServiceRestartCommand} "$@"
'';

linuxSystemdUnit = {
Unit = {
Description = "clawde persistent agents supervisor";
Expand DownExpand Up@@ -118,8 +128,15 @@ in
clawdeSessionStarter
clawdeGracefulRedeploy
clawdeHeartbeatChangeGate
clawdeSupervisorRefresh
];

home.activation.refreshClawdeSupervisorWhenItsCodeChanges =
lib.hm.dag.entryAfter [ "writeBoundary" ]
''
run ${clawdeSupervisorRefresh}/bin/clawde-supervisor-refresh
'';

xdg.dataFile."bash-completion/completions/clawde".source = ../scripts/completion/clawde.bash;

systemd.user.services = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
Expand Down
73 changes: 73 additions & 0 deletions module/scripts/clawde-supervisor-refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import argparse
import os
import subprocess

SUPERVISOR_PROCESS_MATCH_PATTERN = "clawde-service.py --specification-file"


def find_supervisor_process_ids() -> list[int]:
pgrep_result = subprocess.run(
["pgrep", "-f", SUPERVISOR_PROCESS_MATCH_PATTERN],
capture_output=True,
text=True,
)
own_process_id = os.getpid()
return [
int(line)
for line in pgrep_result.stdout.split()
if line.strip().isdigit() and int(line) != own_process_id
]


def read_full_command_line(process_id: int) -> str:
ps_result = subprocess.run(
["ps", "-ww", "-o", "command=", "-p", str(process_id)],
capture_output=True,
text=True,
)
return ps_result.stdout.strip()


def read_deployed_command(deployed_command_file: str) -> str:
with open(deployed_command_file) as command_file:
return command_file.read().strip()


def supervisor_runs_superseded_code(deployed_command: str) -> bool:
for process_id in find_supervisor_process_ids():
command_line = read_full_command_line(process_id)
if command_line and command_line != deployed_command:
return True
return False


def restart_the_supervisor(restart_command: str) -> int:
return subprocess.run(["sh", "-c", restart_command]).returncode


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="clawde-supervisor-refresh",
description="Restart the clawde supervisor when the code it is running is "
"no longer the code this generation deploys. The unit is marked "
"X-RestartIfChanged=false so a rebuild never disturbs a healthy supervisor, "
"which also means new supervisor code would otherwise stay dormant until "
"someone restarted it by hand.",
)
parser.add_argument("--deployed-command-file", required=True)
parser.add_argument("--restart-command", required=True)
return parser.parse_args()


def main() -> int:
arguments = parse_arguments()
deployed_command = read_deployed_command(arguments.deployed_command_file)
if not supervisor_runs_superseded_code(deployed_command):
print("clawde supervisor already runs this generation's code.")
return 0
print("clawde supervisor runs superseded code; restarting it.")
return restart_the_supervisor(arguments.restart_command)


if __name__ == "__main__":
raise SystemExit(main())
107 changes: 107 additions & 0 deletions module/scripts/tests/unit/test_clawde_supervisor_refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
import importlib.util
import pathlib

CLAWDE_SCRIPTS_DIRECTORY = pathlib.Path(__file__).resolve().parent.parent.parent


def _load_supervisor_refresh_module():
module_spec = importlib.util.spec_from_file_location(
"clawde_supervisor_refresh",
CLAWDE_SCRIPTS_DIRECTORY / "clawde-supervisor-refresh.py",
)
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module


supervisor_refresh = _load_supervisor_refresh_module()

DEPLOYED_COMMAND = "/nix/store/new-python3 /nix/store/new-clawde-service.py --specification-file /nix/store/new-spec.json"
SUPERSEDED_COMMAND = "/nix/store/old-python3 /nix/store/old-clawde-service.py --specification-file /nix/store/old-spec.json"


def stub_live_supervisors(monkeypatch, command_lines):
process_ids = list(range(100, 100 + len(command_lines)))
monkeypatch.setattr(
supervisor_refresh, "find_supervisor_process_ids", lambda: process_ids
)
monkeypatch.setattr(
supervisor_refresh,
"read_full_command_line",
lambda process_id: command_lines[process_ids.index(process_id)],
)


def test_a_supervisor_running_superseded_code_is_restarted(monkeypatch, tmp_path):
stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])

assert supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_a_supervisor_already_on_this_generation_is_left_alone(monkeypatch):
stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_no_running_supervisor_needs_no_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_an_unreadable_command_line_never_triggers_a_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [""])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_the_refresh_never_matches_its_own_process(monkeypatch):
class PgrepResult:
stdout = f"{__import__('os').getpid()}\n"

monkeypatch.setattr(
supervisor_refresh.subprocess, "run", lambda *a, **k: PgrepResult()
)

assert supervisor_refresh.find_supervisor_process_ids() == []


def test_the_deployed_command_is_read_from_its_file(tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND + "\n")

assert (
supervisor_refresh.read_deployed_command(str(command_file)) == DEPLOYED_COMMAND
)


def test_main_restarts_only_when_the_running_code_is_superseded(monkeypatch, tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND)
restart_commands = []
monkeypatch.setattr(
supervisor_refresh,
"parse_arguments",
lambda: type(
"Arguments",
(),
{
"deployed_command_file": str(command_file),
"restart_command": "restart-me",
},
),
)
monkeypatch.setattr(
supervisor_refresh,
"restart_the_supervisor",
lambda command: restart_commands.append(command) or 0,
)

stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == []

stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == ["restart-me"]
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Restart the supervisor when its own code changes by Castrozan · Pull Request #21 · Castrozan/clawde · GitHub
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
19 changes: 18 additions & 1 deletion module/config/service.nix
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ let

clawdeServiceRestartCommand =
if pkgs.stdenv.hostPlatform.isLinux then
"systemctl --user restart clawde"
"systemctl --user daemon-reload && systemctl --user restart clawde"
else
"launchctl kickstart -k gui/UID/org.nix-community.home.clawde";

Expand DownExpand Up@@ -55,6 +55,16 @@ let
"${clawdeServiceSpecificationFile}"
];

clawdeServiceDeployedCommandFile = pkgs.writeText "clawde-service-deployed-command" (
lib.concatStringsSep " " clawdeServiceExecArguments
);

clawdeSupervisorRefresh = pkgs.writeShellScriptBin "clawde-supervisor-refresh" ''
exec ${pkgs.python312}/bin/python3 ${../scripts/clawde-supervisor-refresh.py} \
--deployed-command-file ${clawdeServiceDeployedCommandFile} \
--restart-command ${lib.escapeShellArg clawdeServiceRestartCommand} "$@"
'';

linuxSystemdUnit = {
Unit = {
Description = "clawde persistent agents supervisor";
Expand DownExpand Up@@ -118,8 +128,15 @@ in
clawdeSessionStarter
clawdeGracefulRedeploy
clawdeHeartbeatChangeGate
clawdeSupervisorRefresh
];

home.activation.refreshClawdeSupervisorWhenItsCodeChanges =
lib.hm.dag.entryAfter [ "writeBoundary" ]
''
run ${clawdeSupervisorRefresh}/bin/clawde-supervisor-refresh
'';

xdg.dataFile."bash-completion/completions/clawde".source = ../scripts/completion/clawde.bash;

systemd.user.services = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
Expand Down
73 changes: 73 additions & 0 deletions module/scripts/clawde-supervisor-refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import argparse
import os
import subprocess

SUPERVISOR_PROCESS_MATCH_PATTERN = "clawde-service.py --specification-file"


def find_supervisor_process_ids() -> list[int]:
pgrep_result = subprocess.run(
["pgrep", "-f", SUPERVISOR_PROCESS_MATCH_PATTERN],
capture_output=True,
text=True,
)
own_process_id = os.getpid()
return [
int(line)
for line in pgrep_result.stdout.split()
if line.strip().isdigit() and int(line) != own_process_id
]


def read_full_command_line(process_id: int) -> str:
ps_result = subprocess.run(
["ps", "-ww", "-o", "command=", "-p", str(process_id)],
capture_output=True,
text=True,
)
return ps_result.stdout.strip()


def read_deployed_command(deployed_command_file: str) -> str:
with open(deployed_command_file) as command_file:
return command_file.read().strip()


def supervisor_runs_superseded_code(deployed_command: str) -> bool:
for process_id in find_supervisor_process_ids():
command_line = read_full_command_line(process_id)
if command_line and command_line != deployed_command:
return True
return False


def restart_the_supervisor(restart_command: str) -> int:
return subprocess.run(["sh", "-c", restart_command]).returncode


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="clawde-supervisor-refresh",
description="Restart the clawde supervisor when the code it is running is "
"no longer the code this generation deploys. The unit is marked "
"X-RestartIfChanged=false so a rebuild never disturbs a healthy supervisor, "
"which also means new supervisor code would otherwise stay dormant until "
"someone restarted it by hand.",
)
parser.add_argument("--deployed-command-file", required=True)
parser.add_argument("--restart-command", required=True)
return parser.parse_args()


def main() -> int:
arguments = parse_arguments()
deployed_command = read_deployed_command(arguments.deployed_command_file)
if not supervisor_runs_superseded_code(deployed_command):
print("clawde supervisor already runs this generation's code.")
return 0
print("clawde supervisor runs superseded code; restarting it.")
return restart_the_supervisor(arguments.restart_command)


if __name__ == "__main__":
raise SystemExit(main())
107 changes: 107 additions & 0 deletions module/scripts/tests/unit/test_clawde_supervisor_refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
import importlib.util
import pathlib

CLAWDE_SCRIPTS_DIRECTORY = pathlib.Path(__file__).resolve().parent.parent.parent


def _load_supervisor_refresh_module():
module_spec = importlib.util.spec_from_file_location(
"clawde_supervisor_refresh",
CLAWDE_SCRIPTS_DIRECTORY / "clawde-supervisor-refresh.py",
)
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module


supervisor_refresh = _load_supervisor_refresh_module()

DEPLOYED_COMMAND = "/nix/store/new-python3 /nix/store/new-clawde-service.py --specification-file /nix/store/new-spec.json"
SUPERSEDED_COMMAND = "/nix/store/old-python3 /nix/store/old-clawde-service.py --specification-file /nix/store/old-spec.json"


def stub_live_supervisors(monkeypatch, command_lines):
process_ids = list(range(100, 100 + len(command_lines)))
monkeypatch.setattr(
supervisor_refresh, "find_supervisor_process_ids", lambda: process_ids
)
monkeypatch.setattr(
supervisor_refresh,
"read_full_command_line",
lambda process_id: command_lines[process_ids.index(process_id)],
)


def test_a_supervisor_running_superseded_code_is_restarted(monkeypatch, tmp_path):
stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])

assert supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_a_supervisor_already_on_this_generation_is_left_alone(monkeypatch):
stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_no_running_supervisor_needs_no_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_an_unreadable_command_line_never_triggers_a_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [""])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_the_refresh_never_matches_its_own_process(monkeypatch):
class PgrepResult:
stdout = f"{__import__('os').getpid()}\n"

monkeypatch.setattr(
supervisor_refresh.subprocess, "run", lambda *a, **k: PgrepResult()
)

assert supervisor_refresh.find_supervisor_process_ids() == []


def test_the_deployed_command_is_read_from_its_file(tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND + "\n")

assert (
supervisor_refresh.read_deployed_command(str(command_file)) == DEPLOYED_COMMAND
)


def test_main_restarts_only_when_the_running_code_is_superseded(monkeypatch, tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND)
restart_commands = []
monkeypatch.setattr(
supervisor_refresh,
"parse_arguments",
lambda: type(
"Arguments",
(),
{
"deployed_command_file": str(command_file),
"restart_command": "restart-me",
},
),
)
monkeypatch.setattr(
supervisor_refresh,
"restart_the_supervisor",
lambda command: restart_commands.append(command) or 0,
)

stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == []

stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == ["restart-me"]
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Restart the supervisor when its own code changes by Castrozan · Pull Request #21 · Castrozan/clawde · GitHub
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
19 changes: 18 additions & 1 deletion module/config/service.nix
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ let

clawdeServiceRestartCommand =
if pkgs.stdenv.hostPlatform.isLinux then
"systemctl --user restart clawde"
"systemctl --user daemon-reload && systemctl --user restart clawde"
else
"launchctl kickstart -k gui/UID/org.nix-community.home.clawde";

Expand DownExpand Up@@ -55,6 +55,16 @@ let
"${clawdeServiceSpecificationFile}"
];

clawdeServiceDeployedCommandFile = pkgs.writeText "clawde-service-deployed-command" (
lib.concatStringsSep " " clawdeServiceExecArguments
);

clawdeSupervisorRefresh = pkgs.writeShellScriptBin "clawde-supervisor-refresh" ''
exec ${pkgs.python312}/bin/python3 ${../scripts/clawde-supervisor-refresh.py} \
--deployed-command-file ${clawdeServiceDeployedCommandFile} \
--restart-command ${lib.escapeShellArg clawdeServiceRestartCommand} "$@"
'';

linuxSystemdUnit = {
Unit = {
Description = "clawde persistent agents supervisor";
Expand DownExpand Up@@ -118,8 +128,15 @@ in
clawdeSessionStarter
clawdeGracefulRedeploy
clawdeHeartbeatChangeGate
clawdeSupervisorRefresh
];

home.activation.refreshClawdeSupervisorWhenItsCodeChanges =
lib.hm.dag.entryAfter [ "writeBoundary" ]
''
run ${clawdeSupervisorRefresh}/bin/clawde-supervisor-refresh
'';

xdg.dataFile."bash-completion/completions/clawde".source = ../scripts/completion/clawde.bash;

systemd.user.services = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
Expand Down
73 changes: 73 additions & 0 deletions module/scripts/clawde-supervisor-refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import argparse
import os
import subprocess

SUPERVISOR_PROCESS_MATCH_PATTERN = "clawde-service.py --specification-file"


def find_supervisor_process_ids() -> list[int]:
pgrep_result = subprocess.run(
["pgrep", "-f", SUPERVISOR_PROCESS_MATCH_PATTERN],
capture_output=True,
text=True,
)
own_process_id = os.getpid()
return [
int(line)
for line in pgrep_result.stdout.split()
if line.strip().isdigit() and int(line) != own_process_id
]


def read_full_command_line(process_id: int) -> str:
ps_result = subprocess.run(
["ps", "-ww", "-o", "command=", "-p", str(process_id)],
capture_output=True,
text=True,
)
return ps_result.stdout.strip()


def read_deployed_command(deployed_command_file: str) -> str:
with open(deployed_command_file) as command_file:
return command_file.read().strip()


def supervisor_runs_superseded_code(deployed_command: str) -> bool:
for process_id in find_supervisor_process_ids():
command_line = read_full_command_line(process_id)
if command_line and command_line != deployed_command:
return True
return False


def restart_the_supervisor(restart_command: str) -> int:
return subprocess.run(["sh", "-c", restart_command]).returncode


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="clawde-supervisor-refresh",
description="Restart the clawde supervisor when the code it is running is "
"no longer the code this generation deploys. The unit is marked "
"X-RestartIfChanged=false so a rebuild never disturbs a healthy supervisor, "
"which also means new supervisor code would otherwise stay dormant until "
"someone restarted it by hand.",
)
parser.add_argument("--deployed-command-file", required=True)
parser.add_argument("--restart-command", required=True)
return parser.parse_args()


def main() -> int:
arguments = parse_arguments()
deployed_command = read_deployed_command(arguments.deployed_command_file)
if not supervisor_runs_superseded_code(deployed_command):
print("clawde supervisor already runs this generation's code.")
return 0
print("clawde supervisor runs superseded code; restarting it.")
return restart_the_supervisor(arguments.restart_command)


if __name__ == "__main__":
raise SystemExit(main())
107 changes: 107 additions & 0 deletions module/scripts/tests/unit/test_clawde_supervisor_refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
import importlib.util
import pathlib

CLAWDE_SCRIPTS_DIRECTORY = pathlib.Path(__file__).resolve().parent.parent.parent


def _load_supervisor_refresh_module():
module_spec = importlib.util.spec_from_file_location(
"clawde_supervisor_refresh",
CLAWDE_SCRIPTS_DIRECTORY / "clawde-supervisor-refresh.py",
)
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module


supervisor_refresh = _load_supervisor_refresh_module()

DEPLOYED_COMMAND = "/nix/store/new-python3 /nix/store/new-clawde-service.py --specification-file /nix/store/new-spec.json"
SUPERSEDED_COMMAND = "/nix/store/old-python3 /nix/store/old-clawde-service.py --specification-file /nix/store/old-spec.json"


def stub_live_supervisors(monkeypatch, command_lines):
process_ids = list(range(100, 100 + len(command_lines)))
monkeypatch.setattr(
supervisor_refresh, "find_supervisor_process_ids", lambda: process_ids
)
monkeypatch.setattr(
supervisor_refresh,
"read_full_command_line",
lambda process_id: command_lines[process_ids.index(process_id)],
)


def test_a_supervisor_running_superseded_code_is_restarted(monkeypatch, tmp_path):
stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])

assert supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_a_supervisor_already_on_this_generation_is_left_alone(monkeypatch):
stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_no_running_supervisor_needs_no_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_an_unreadable_command_line_never_triggers_a_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [""])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_the_refresh_never_matches_its_own_process(monkeypatch):
class PgrepResult:
stdout = f"{__import__('os').getpid()}\n"

monkeypatch.setattr(
supervisor_refresh.subprocess, "run", lambda *a, **k: PgrepResult()
)

assert supervisor_refresh.find_supervisor_process_ids() == []


def test_the_deployed_command_is_read_from_its_file(tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND + "\n")

assert (
supervisor_refresh.read_deployed_command(str(command_file)) == DEPLOYED_COMMAND
)


def test_main_restarts_only_when_the_running_code_is_superseded(monkeypatch, tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND)
restart_commands = []
monkeypatch.setattr(
supervisor_refresh,
"parse_arguments",
lambda: type(
"Arguments",
(),
{
"deployed_command_file": str(command_file),
"restart_command": "restart-me",
},
),
)
monkeypatch.setattr(
supervisor_refresh,
"restart_the_supervisor",
lambda command: restart_commands.append(command) or 0,
)

stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == []

stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == ["restart-me"]
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Restart the supervisor when its own code changes by Castrozan · Pull Request #21 · Castrozan/clawde · GitHub
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
19 changes: 18 additions & 1 deletion module/config/service.nix
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ let

clawdeServiceRestartCommand =
if pkgs.stdenv.hostPlatform.isLinux then
"systemctl --user restart clawde"
"systemctl --user daemon-reload && systemctl --user restart clawde"
else
"launchctl kickstart -k gui/UID/org.nix-community.home.clawde";

Expand DownExpand Up@@ -55,6 +55,16 @@ let
"${clawdeServiceSpecificationFile}"
];

clawdeServiceDeployedCommandFile = pkgs.writeText "clawde-service-deployed-command" (
lib.concatStringsSep " " clawdeServiceExecArguments
);

clawdeSupervisorRefresh = pkgs.writeShellScriptBin "clawde-supervisor-refresh" ''
exec ${pkgs.python312}/bin/python3 ${../scripts/clawde-supervisor-refresh.py} \
--deployed-command-file ${clawdeServiceDeployedCommandFile} \
--restart-command ${lib.escapeShellArg clawdeServiceRestartCommand} "$@"
'';

linuxSystemdUnit = {
Unit = {
Description = "clawde persistent agents supervisor";
Expand DownExpand Up@@ -118,8 +128,15 @@ in
clawdeSessionStarter
clawdeGracefulRedeploy
clawdeHeartbeatChangeGate
clawdeSupervisorRefresh
];

home.activation.refreshClawdeSupervisorWhenItsCodeChanges =
lib.hm.dag.entryAfter [ "writeBoundary" ]
''
run ${clawdeSupervisorRefresh}/bin/clawde-supervisor-refresh
'';

xdg.dataFile."bash-completion/completions/clawde".source = ../scripts/completion/clawde.bash;

systemd.user.services = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
Expand Down
73 changes: 73 additions & 0 deletions module/scripts/clawde-supervisor-refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import argparse
import os
import subprocess

SUPERVISOR_PROCESS_MATCH_PATTERN = "clawde-service.py --specification-file"


def find_supervisor_process_ids() -> list[int]:
pgrep_result = subprocess.run(
["pgrep", "-f", SUPERVISOR_PROCESS_MATCH_PATTERN],
capture_output=True,
text=True,
)
own_process_id = os.getpid()
return [
int(line)
for line in pgrep_result.stdout.split()
if line.strip().isdigit() and int(line) != own_process_id
]


def read_full_command_line(process_id: int) -> str:
ps_result = subprocess.run(
["ps", "-ww", "-o", "command=", "-p", str(process_id)],
capture_output=True,
text=True,
)
return ps_result.stdout.strip()


def read_deployed_command(deployed_command_file: str) -> str:
with open(deployed_command_file) as command_file:
return command_file.read().strip()


def supervisor_runs_superseded_code(deployed_command: str) -> bool:
for process_id in find_supervisor_process_ids():
command_line = read_full_command_line(process_id)
if command_line and command_line != deployed_command:
return True
return False


def restart_the_supervisor(restart_command: str) -> int:
return subprocess.run(["sh", "-c", restart_command]).returncode


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="clawde-supervisor-refresh",
description="Restart the clawde supervisor when the code it is running is "
"no longer the code this generation deploys. The unit is marked "
"X-RestartIfChanged=false so a rebuild never disturbs a healthy supervisor, "
"which also means new supervisor code would otherwise stay dormant until "
"someone restarted it by hand.",
)
parser.add_argument("--deployed-command-file", required=True)
parser.add_argument("--restart-command", required=True)
return parser.parse_args()


def main() -> int:
arguments = parse_arguments()
deployed_command = read_deployed_command(arguments.deployed_command_file)
if not supervisor_runs_superseded_code(deployed_command):
print("clawde supervisor already runs this generation's code.")
return 0
print("clawde supervisor runs superseded code; restarting it.")
return restart_the_supervisor(arguments.restart_command)


if __name__ == "__main__":
raise SystemExit(main())
107 changes: 107 additions & 0 deletions module/scripts/tests/unit/test_clawde_supervisor_refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
import importlib.util
import pathlib

CLAWDE_SCRIPTS_DIRECTORY = pathlib.Path(__file__).resolve().parent.parent.parent


def _load_supervisor_refresh_module():
module_spec = importlib.util.spec_from_file_location(
"clawde_supervisor_refresh",
CLAWDE_SCRIPTS_DIRECTORY / "clawde-supervisor-refresh.py",
)
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module


supervisor_refresh = _load_supervisor_refresh_module()

DEPLOYED_COMMAND = "/nix/store/new-python3 /nix/store/new-clawde-service.py --specification-file /nix/store/new-spec.json"
SUPERSEDED_COMMAND = "/nix/store/old-python3 /nix/store/old-clawde-service.py --specification-file /nix/store/old-spec.json"


def stub_live_supervisors(monkeypatch, command_lines):
process_ids = list(range(100, 100 + len(command_lines)))
monkeypatch.setattr(
supervisor_refresh, "find_supervisor_process_ids", lambda: process_ids
)
monkeypatch.setattr(
supervisor_refresh,
"read_full_command_line",
lambda process_id: command_lines[process_ids.index(process_id)],
)


def test_a_supervisor_running_superseded_code_is_restarted(monkeypatch, tmp_path):
stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])

assert supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_a_supervisor_already_on_this_generation_is_left_alone(monkeypatch):
stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_no_running_supervisor_needs_no_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_an_unreadable_command_line_never_triggers_a_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [""])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_the_refresh_never_matches_its_own_process(monkeypatch):
class PgrepResult:
stdout = f"{__import__('os').getpid()}\n"

monkeypatch.setattr(
supervisor_refresh.subprocess, "run", lambda *a, **k: PgrepResult()
)

assert supervisor_refresh.find_supervisor_process_ids() == []


def test_the_deployed_command_is_read_from_its_file(tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND + "\n")

assert (
supervisor_refresh.read_deployed_command(str(command_file)) == DEPLOYED_COMMAND
)


def test_main_restarts_only_when_the_running_code_is_superseded(monkeypatch, tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND)
restart_commands = []
monkeypatch.setattr(
supervisor_refresh,
"parse_arguments",
lambda: type(
"Arguments",
(),
{
"deployed_command_file": str(command_file),
"restart_command": "restart-me",
},
),
)
monkeypatch.setattr(
supervisor_refresh,
"restart_the_supervisor",
lambda command: restart_commands.append(command) or 0,
)

stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == []

stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == ["restart-me"]
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Restart the supervisor when its own code changes by Castrozan · Pull Request #21 · Castrozan/clawde · GitHub
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
19 changes: 18 additions & 1 deletion module/config/service.nix
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ let

clawdeServiceRestartCommand =
if pkgs.stdenv.hostPlatform.isLinux then
"systemctl --user restart clawde"
"systemctl --user daemon-reload && systemctl --user restart clawde"
else
"launchctl kickstart -k gui/UID/org.nix-community.home.clawde";

Expand DownExpand Up@@ -55,6 +55,16 @@ let
"${clawdeServiceSpecificationFile}"
];

clawdeServiceDeployedCommandFile = pkgs.writeText "clawde-service-deployed-command" (
lib.concatStringsSep " " clawdeServiceExecArguments
);

clawdeSupervisorRefresh = pkgs.writeShellScriptBin "clawde-supervisor-refresh" ''
exec ${pkgs.python312}/bin/python3 ${../scripts/clawde-supervisor-refresh.py} \
--deployed-command-file ${clawdeServiceDeployedCommandFile} \
--restart-command ${lib.escapeShellArg clawdeServiceRestartCommand} "$@"
'';

linuxSystemdUnit = {
Unit = {
Description = "clawde persistent agents supervisor";
Expand DownExpand Up@@ -118,8 +128,15 @@ in
clawdeSessionStarter
clawdeGracefulRedeploy
clawdeHeartbeatChangeGate
clawdeSupervisorRefresh
];

home.activation.refreshClawdeSupervisorWhenItsCodeChanges =
lib.hm.dag.entryAfter [ "writeBoundary" ]
''
run ${clawdeSupervisorRefresh}/bin/clawde-supervisor-refresh
'';

xdg.dataFile."bash-completion/completions/clawde".source = ../scripts/completion/clawde.bash;

systemd.user.services = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
Expand Down
73 changes: 73 additions & 0 deletions module/scripts/clawde-supervisor-refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import argparse
import os
import subprocess

SUPERVISOR_PROCESS_MATCH_PATTERN = "clawde-service.py --specification-file"


def find_supervisor_process_ids() -> list[int]:
pgrep_result = subprocess.run(
["pgrep", "-f", SUPERVISOR_PROCESS_MATCH_PATTERN],
capture_output=True,
text=True,
)
own_process_id = os.getpid()
return [
int(line)
for line in pgrep_result.stdout.split()
if line.strip().isdigit() and int(line) != own_process_id
]


def read_full_command_line(process_id: int) -> str:
ps_result = subprocess.run(
["ps", "-ww", "-o", "command=", "-p", str(process_id)],
capture_output=True,
text=True,
)
return ps_result.stdout.strip()


def read_deployed_command(deployed_command_file: str) -> str:
with open(deployed_command_file) as command_file:
return command_file.read().strip()


def supervisor_runs_superseded_code(deployed_command: str) -> bool:
for process_id in find_supervisor_process_ids():
command_line = read_full_command_line(process_id)
if command_line and command_line != deployed_command:
return True
return False


def restart_the_supervisor(restart_command: str) -> int:
return subprocess.run(["sh", "-c", restart_command]).returncode


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="clawde-supervisor-refresh",
description="Restart the clawde supervisor when the code it is running is "
"no longer the code this generation deploys. The unit is marked "
"X-RestartIfChanged=false so a rebuild never disturbs a healthy supervisor, "
"which also means new supervisor code would otherwise stay dormant until "
"someone restarted it by hand.",
)
parser.add_argument("--deployed-command-file", required=True)
parser.add_argument("--restart-command", required=True)
return parser.parse_args()


def main() -> int:
arguments = parse_arguments()
deployed_command = read_deployed_command(arguments.deployed_command_file)
if not supervisor_runs_superseded_code(deployed_command):
print("clawde supervisor already runs this generation's code.")
return 0
print("clawde supervisor runs superseded code; restarting it.")
return restart_the_supervisor(arguments.restart_command)


if __name__ == "__main__":
raise SystemExit(main())
107 changes: 107 additions & 0 deletions module/scripts/tests/unit/test_clawde_supervisor_refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
import importlib.util
import pathlib

CLAWDE_SCRIPTS_DIRECTORY = pathlib.Path(__file__).resolve().parent.parent.parent


def _load_supervisor_refresh_module():
module_spec = importlib.util.spec_from_file_location(
"clawde_supervisor_refresh",
CLAWDE_SCRIPTS_DIRECTORY / "clawde-supervisor-refresh.py",
)
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module


supervisor_refresh = _load_supervisor_refresh_module()

DEPLOYED_COMMAND = "/nix/store/new-python3 /nix/store/new-clawde-service.py --specification-file /nix/store/new-spec.json"
SUPERSEDED_COMMAND = "/nix/store/old-python3 /nix/store/old-clawde-service.py --specification-file /nix/store/old-spec.json"


def stub_live_supervisors(monkeypatch, command_lines):
process_ids = list(range(100, 100 + len(command_lines)))
monkeypatch.setattr(
supervisor_refresh, "find_supervisor_process_ids", lambda: process_ids
)
monkeypatch.setattr(
supervisor_refresh,
"read_full_command_line",
lambda process_id: command_lines[process_ids.index(process_id)],
)


def test_a_supervisor_running_superseded_code_is_restarted(monkeypatch, tmp_path):
stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])

assert supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_a_supervisor_already_on_this_generation_is_left_alone(monkeypatch):
stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_no_running_supervisor_needs_no_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_an_unreadable_command_line_never_triggers_a_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [""])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_the_refresh_never_matches_its_own_process(monkeypatch):
class PgrepResult:
stdout = f"{__import__('os').getpid()}\n"

monkeypatch.setattr(
supervisor_refresh.subprocess, "run", lambda *a, **k: PgrepResult()
)

assert supervisor_refresh.find_supervisor_process_ids() == []


def test_the_deployed_command_is_read_from_its_file(tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND + "\n")

assert (
supervisor_refresh.read_deployed_command(str(command_file)) == DEPLOYED_COMMAND
)


def test_main_restarts_only_when_the_running_code_is_superseded(monkeypatch, tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND)
restart_commands = []
monkeypatch.setattr(
supervisor_refresh,
"parse_arguments",
lambda: type(
"Arguments",
(),
{
"deployed_command_file": str(command_file),
"restart_command": "restart-me",
},
),
)
monkeypatch.setattr(
supervisor_refresh,
"restart_the_supervisor",
lambda command: restart_commands.append(command) or 0,
)

stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == []

stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == ["restart-me"]
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Restart the supervisor when its own code changes by Castrozan · Pull Request #21 · Castrozan/clawde · GitHub
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
19 changes: 18 additions & 1 deletion module/config/service.nix
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ let

clawdeServiceRestartCommand =
if pkgs.stdenv.hostPlatform.isLinux then
"systemctl --user restart clawde"
"systemctl --user daemon-reload && systemctl --user restart clawde"
else
"launchctl kickstart -k gui/UID/org.nix-community.home.clawde";

Expand DownExpand Up@@ -55,6 +55,16 @@ let
"${clawdeServiceSpecificationFile}"
];

clawdeServiceDeployedCommandFile = pkgs.writeText "clawde-service-deployed-command" (
lib.concatStringsSep " " clawdeServiceExecArguments
);

clawdeSupervisorRefresh = pkgs.writeShellScriptBin "clawde-supervisor-refresh" ''
exec ${pkgs.python312}/bin/python3 ${../scripts/clawde-supervisor-refresh.py} \
--deployed-command-file ${clawdeServiceDeployedCommandFile} \
--restart-command ${lib.escapeShellArg clawdeServiceRestartCommand} "$@"
'';

linuxSystemdUnit = {
Unit = {
Description = "clawde persistent agents supervisor";
Expand DownExpand Up@@ -118,8 +128,15 @@ in
clawdeSessionStarter
clawdeGracefulRedeploy
clawdeHeartbeatChangeGate
clawdeSupervisorRefresh
];

home.activation.refreshClawdeSupervisorWhenItsCodeChanges =
lib.hm.dag.entryAfter [ "writeBoundary" ]
''
run ${clawdeSupervisorRefresh}/bin/clawde-supervisor-refresh
'';

xdg.dataFile."bash-completion/completions/clawde".source = ../scripts/completion/clawde.bash;

systemd.user.services = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
Expand Down
73 changes: 73 additions & 0 deletions module/scripts/clawde-supervisor-refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import argparse
import os
import subprocess

SUPERVISOR_PROCESS_MATCH_PATTERN = "clawde-service.py --specification-file"


def find_supervisor_process_ids() -> list[int]:
pgrep_result = subprocess.run(
["pgrep", "-f", SUPERVISOR_PROCESS_MATCH_PATTERN],
capture_output=True,
text=True,
)
own_process_id = os.getpid()
return [
int(line)
for line in pgrep_result.stdout.split()
if line.strip().isdigit() and int(line) != own_process_id
]


def read_full_command_line(process_id: int) -> str:
ps_result = subprocess.run(
["ps", "-ww", "-o", "command=", "-p", str(process_id)],
capture_output=True,
text=True,
)
return ps_result.stdout.strip()


def read_deployed_command(deployed_command_file: str) -> str:
with open(deployed_command_file) as command_file:
return command_file.read().strip()


def supervisor_runs_superseded_code(deployed_command: str) -> bool:
for process_id in find_supervisor_process_ids():
command_line = read_full_command_line(process_id)
if command_line and command_line != deployed_command:
return True
return False


def restart_the_supervisor(restart_command: str) -> int:
return subprocess.run(["sh", "-c", restart_command]).returncode


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="clawde-supervisor-refresh",
description="Restart the clawde supervisor when the code it is running is "
"no longer the code this generation deploys. The unit is marked "
"X-RestartIfChanged=false so a rebuild never disturbs a healthy supervisor, "
"which also means new supervisor code would otherwise stay dormant until "
"someone restarted it by hand.",
)
parser.add_argument("--deployed-command-file", required=True)
parser.add_argument("--restart-command", required=True)
return parser.parse_args()


def main() -> int:
arguments = parse_arguments()
deployed_command = read_deployed_command(arguments.deployed_command_file)
if not supervisor_runs_superseded_code(deployed_command):
print("clawde supervisor already runs this generation's code.")
return 0
print("clawde supervisor runs superseded code; restarting it.")
return restart_the_supervisor(arguments.restart_command)


if __name__ == "__main__":
raise SystemExit(main())
107 changes: 107 additions & 0 deletions module/scripts/tests/unit/test_clawde_supervisor_refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
import importlib.util
import pathlib

CLAWDE_SCRIPTS_DIRECTORY = pathlib.Path(__file__).resolve().parent.parent.parent


def _load_supervisor_refresh_module():
module_spec = importlib.util.spec_from_file_location(
"clawde_supervisor_refresh",
CLAWDE_SCRIPTS_DIRECTORY / "clawde-supervisor-refresh.py",
)
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module


supervisor_refresh = _load_supervisor_refresh_module()

DEPLOYED_COMMAND = "/nix/store/new-python3 /nix/store/new-clawde-service.py --specification-file /nix/store/new-spec.json"
SUPERSEDED_COMMAND = "/nix/store/old-python3 /nix/store/old-clawde-service.py --specification-file /nix/store/old-spec.json"


def stub_live_supervisors(monkeypatch, command_lines):
process_ids = list(range(100, 100 + len(command_lines)))
monkeypatch.setattr(
supervisor_refresh, "find_supervisor_process_ids", lambda: process_ids
)
monkeypatch.setattr(
supervisor_refresh,
"read_full_command_line",
lambda process_id: command_lines[process_ids.index(process_id)],
)


def test_a_supervisor_running_superseded_code_is_restarted(monkeypatch, tmp_path):
stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])

assert supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_a_supervisor_already_on_this_generation_is_left_alone(monkeypatch):
stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_no_running_supervisor_needs_no_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_an_unreadable_command_line_never_triggers_a_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [""])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_the_refresh_never_matches_its_own_process(monkeypatch):
class PgrepResult:
stdout = f"{__import__('os').getpid()}\n"

monkeypatch.setattr(
supervisor_refresh.subprocess, "run", lambda *a, **k: PgrepResult()
)

assert supervisor_refresh.find_supervisor_process_ids() == []


def test_the_deployed_command_is_read_from_its_file(tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND + "\n")

assert (
supervisor_refresh.read_deployed_command(str(command_file)) == DEPLOYED_COMMAND
)


def test_main_restarts_only_when_the_running_code_is_superseded(monkeypatch, tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND)
restart_commands = []
monkeypatch.setattr(
supervisor_refresh,
"parse_arguments",
lambda: type(
"Arguments",
(),
{
"deployed_command_file": str(command_file),
"restart_command": "restart-me",
},
),
)
monkeypatch.setattr(
supervisor_refresh,
"restart_the_supervisor",
lambda command: restart_commands.append(command) or 0,
)

stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == []

stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == ["restart-me"]
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Restart the supervisor when its own code changes by Castrozan · Pull Request #21 · Castrozan/clawde · GitHub
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
19 changes: 18 additions & 1 deletion module/config/service.nix
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ let

clawdeServiceRestartCommand =
if pkgs.stdenv.hostPlatform.isLinux then
"systemctl --user restart clawde"
"systemctl --user daemon-reload && systemctl --user restart clawde"
else
"launchctl kickstart -k gui/UID/org.nix-community.home.clawde";

Expand DownExpand Up@@ -55,6 +55,16 @@ let
"${clawdeServiceSpecificationFile}"
];

clawdeServiceDeployedCommandFile = pkgs.writeText "clawde-service-deployed-command" (
lib.concatStringsSep " " clawdeServiceExecArguments
);

clawdeSupervisorRefresh = pkgs.writeShellScriptBin "clawde-supervisor-refresh" ''
exec ${pkgs.python312}/bin/python3 ${../scripts/clawde-supervisor-refresh.py} \
--deployed-command-file ${clawdeServiceDeployedCommandFile} \
--restart-command ${lib.escapeShellArg clawdeServiceRestartCommand} "$@"
'';

linuxSystemdUnit = {
Unit = {
Description = "clawde persistent agents supervisor";
Expand DownExpand Up@@ -118,8 +128,15 @@ in
clawdeSessionStarter
clawdeGracefulRedeploy
clawdeHeartbeatChangeGate
clawdeSupervisorRefresh
];

home.activation.refreshClawdeSupervisorWhenItsCodeChanges =
lib.hm.dag.entryAfter [ "writeBoundary" ]
''
run ${clawdeSupervisorRefresh}/bin/clawde-supervisor-refresh
'';

xdg.dataFile."bash-completion/completions/clawde".source = ../scripts/completion/clawde.bash;

systemd.user.services = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
Expand Down
73 changes: 73 additions & 0 deletions module/scripts/clawde-supervisor-refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import argparse
import os
import subprocess

SUPERVISOR_PROCESS_MATCH_PATTERN = "clawde-service.py --specification-file"


def find_supervisor_process_ids() -> list[int]:
pgrep_result = subprocess.run(
["pgrep", "-f", SUPERVISOR_PROCESS_MATCH_PATTERN],
capture_output=True,
text=True,
)
own_process_id = os.getpid()
return [
int(line)
for line in pgrep_result.stdout.split()
if line.strip().isdigit() and int(line) != own_process_id
]


def read_full_command_line(process_id: int) -> str:
ps_result = subprocess.run(
["ps", "-ww", "-o", "command=", "-p", str(process_id)],
capture_output=True,
text=True,
)
return ps_result.stdout.strip()


def read_deployed_command(deployed_command_file: str) -> str:
with open(deployed_command_file) as command_file:
return command_file.read().strip()


def supervisor_runs_superseded_code(deployed_command: str) -> bool:
for process_id in find_supervisor_process_ids():
command_line = read_full_command_line(process_id)
if command_line and command_line != deployed_command:
return True
return False


def restart_the_supervisor(restart_command: str) -> int:
return subprocess.run(["sh", "-c", restart_command]).returncode


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="clawde-supervisor-refresh",
description="Restart the clawde supervisor when the code it is running is "
"no longer the code this generation deploys. The unit is marked "
"X-RestartIfChanged=false so a rebuild never disturbs a healthy supervisor, "
"which also means new supervisor code would otherwise stay dormant until "
"someone restarted it by hand.",
)
parser.add_argument("--deployed-command-file", required=True)
parser.add_argument("--restart-command", required=True)
return parser.parse_args()


def main() -> int:
arguments = parse_arguments()
deployed_command = read_deployed_command(arguments.deployed_command_file)
if not supervisor_runs_superseded_code(deployed_command):
print("clawde supervisor already runs this generation's code.")
return 0
print("clawde supervisor runs superseded code; restarting it.")
return restart_the_supervisor(arguments.restart_command)


if __name__ == "__main__":
raise SystemExit(main())
107 changes: 107 additions & 0 deletions module/scripts/tests/unit/test_clawde_supervisor_refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
import importlib.util
import pathlib

CLAWDE_SCRIPTS_DIRECTORY = pathlib.Path(__file__).resolve().parent.parent.parent


def _load_supervisor_refresh_module():
module_spec = importlib.util.spec_from_file_location(
"clawde_supervisor_refresh",
CLAWDE_SCRIPTS_DIRECTORY / "clawde-supervisor-refresh.py",
)
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module


supervisor_refresh = _load_supervisor_refresh_module()

DEPLOYED_COMMAND = "/nix/store/new-python3 /nix/store/new-clawde-service.py --specification-file /nix/store/new-spec.json"
SUPERSEDED_COMMAND = "/nix/store/old-python3 /nix/store/old-clawde-service.py --specification-file /nix/store/old-spec.json"


def stub_live_supervisors(monkeypatch, command_lines):
process_ids = list(range(100, 100 + len(command_lines)))
monkeypatch.setattr(
supervisor_refresh, "find_supervisor_process_ids", lambda: process_ids
)
monkeypatch.setattr(
supervisor_refresh,
"read_full_command_line",
lambda process_id: command_lines[process_ids.index(process_id)],
)


def test_a_supervisor_running_superseded_code_is_restarted(monkeypatch, tmp_path):
stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])

assert supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_a_supervisor_already_on_this_generation_is_left_alone(monkeypatch):
stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_no_running_supervisor_needs_no_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_an_unreadable_command_line_never_triggers_a_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [""])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_the_refresh_never_matches_its_own_process(monkeypatch):
class PgrepResult:
stdout = f"{__import__('os').getpid()}\n"

monkeypatch.setattr(
supervisor_refresh.subprocess, "run", lambda *a, **k: PgrepResult()
)

assert supervisor_refresh.find_supervisor_process_ids() == []


def test_the_deployed_command_is_read_from_its_file(tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND + "\n")

assert (
supervisor_refresh.read_deployed_command(str(command_file)) == DEPLOYED_COMMAND
)


def test_main_restarts_only_when_the_running_code_is_superseded(monkeypatch, tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND)
restart_commands = []
monkeypatch.setattr(
supervisor_refresh,
"parse_arguments",
lambda: type(
"Arguments",
(),
{
"deployed_command_file": str(command_file),
"restart_command": "restart-me",
},
),
)
monkeypatch.setattr(
supervisor_refresh,
"restart_the_supervisor",
lambda command: restart_commands.append(command) or 0,
)

stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == []

stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == ["restart-me"]
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Restart the supervisor when its own code changes by Castrozan · Pull Request #21 · Castrozan/clawde · GitHub
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
19 changes: 18 additions & 1 deletion module/config/service.nix
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ let

clawdeServiceRestartCommand =
if pkgs.stdenv.hostPlatform.isLinux then
"systemctl --user restart clawde"
"systemctl --user daemon-reload && systemctl --user restart clawde"
else
"launchctl kickstart -k gui/UID/org.nix-community.home.clawde";

Expand DownExpand Up@@ -55,6 +55,16 @@ let
"${clawdeServiceSpecificationFile}"
];

clawdeServiceDeployedCommandFile = pkgs.writeText "clawde-service-deployed-command" (
lib.concatStringsSep " " clawdeServiceExecArguments
);

clawdeSupervisorRefresh = pkgs.writeShellScriptBin "clawde-supervisor-refresh" ''
exec ${pkgs.python312}/bin/python3 ${../scripts/clawde-supervisor-refresh.py} \
--deployed-command-file ${clawdeServiceDeployedCommandFile} \
--restart-command ${lib.escapeShellArg clawdeServiceRestartCommand} "$@"
'';

linuxSystemdUnit = {
Unit = {
Description = "clawde persistent agents supervisor";
Expand DownExpand Up@@ -118,8 +128,15 @@ in
clawdeSessionStarter
clawdeGracefulRedeploy
clawdeHeartbeatChangeGate
clawdeSupervisorRefresh
];

home.activation.refreshClawdeSupervisorWhenItsCodeChanges =
lib.hm.dag.entryAfter [ "writeBoundary" ]
''
run ${clawdeSupervisorRefresh}/bin/clawde-supervisor-refresh
'';

xdg.dataFile."bash-completion/completions/clawde".source = ../scripts/completion/clawde.bash;

systemd.user.services = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
Expand Down
73 changes: 73 additions & 0 deletions module/scripts/clawde-supervisor-refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import argparse
import os
import subprocess

SUPERVISOR_PROCESS_MATCH_PATTERN = "clawde-service.py --specification-file"


def find_supervisor_process_ids() -> list[int]:
pgrep_result = subprocess.run(
["pgrep", "-f", SUPERVISOR_PROCESS_MATCH_PATTERN],
capture_output=True,
text=True,
)
own_process_id = os.getpid()
return [
int(line)
for line in pgrep_result.stdout.split()
if line.strip().isdigit() and int(line) != own_process_id
]


def read_full_command_line(process_id: int) -> str:
ps_result = subprocess.run(
["ps", "-ww", "-o", "command=", "-p", str(process_id)],
capture_output=True,
text=True,
)
return ps_result.stdout.strip()


def read_deployed_command(deployed_command_file: str) -> str:
with open(deployed_command_file) as command_file:
return command_file.read().strip()


def supervisor_runs_superseded_code(deployed_command: str) -> bool:
for process_id in find_supervisor_process_ids():
command_line = read_full_command_line(process_id)
if command_line and command_line != deployed_command:
return True
return False


def restart_the_supervisor(restart_command: str) -> int:
return subprocess.run(["sh", "-c", restart_command]).returncode


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="clawde-supervisor-refresh",
description="Restart the clawde supervisor when the code it is running is "
"no longer the code this generation deploys. The unit is marked "
"X-RestartIfChanged=false so a rebuild never disturbs a healthy supervisor, "
"which also means new supervisor code would otherwise stay dormant until "
"someone restarted it by hand.",
)
parser.add_argument("--deployed-command-file", required=True)
parser.add_argument("--restart-command", required=True)
return parser.parse_args()


def main() -> int:
arguments = parse_arguments()
deployed_command = read_deployed_command(arguments.deployed_command_file)
if not supervisor_runs_superseded_code(deployed_command):
print("clawde supervisor already runs this generation's code.")
return 0
print("clawde supervisor runs superseded code; restarting it.")
return restart_the_supervisor(arguments.restart_command)


if __name__ == "__main__":
raise SystemExit(main())
107 changes: 107 additions & 0 deletions module/scripts/tests/unit/test_clawde_supervisor_refresh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
import importlib.util
import pathlib

CLAWDE_SCRIPTS_DIRECTORY = pathlib.Path(__file__).resolve().parent.parent.parent


def _load_supervisor_refresh_module():
module_spec = importlib.util.spec_from_file_location(
"clawde_supervisor_refresh",
CLAWDE_SCRIPTS_DIRECTORY / "clawde-supervisor-refresh.py",
)
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module


supervisor_refresh = _load_supervisor_refresh_module()

DEPLOYED_COMMAND = "/nix/store/new-python3 /nix/store/new-clawde-service.py --specification-file /nix/store/new-spec.json"
SUPERSEDED_COMMAND = "/nix/store/old-python3 /nix/store/old-clawde-service.py --specification-file /nix/store/old-spec.json"


def stub_live_supervisors(monkeypatch, command_lines):
process_ids = list(range(100, 100 + len(command_lines)))
monkeypatch.setattr(
supervisor_refresh, "find_supervisor_process_ids", lambda: process_ids
)
monkeypatch.setattr(
supervisor_refresh,
"read_full_command_line",
lambda process_id: command_lines[process_ids.index(process_id)],
)


def test_a_supervisor_running_superseded_code_is_restarted(monkeypatch, tmp_path):
stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])

assert supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_a_supervisor_already_on_this_generation_is_left_alone(monkeypatch):
stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_no_running_supervisor_needs_no_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_an_unreadable_command_line_never_triggers_a_restart(monkeypatch):
stub_live_supervisors(monkeypatch, [""])

assert not supervisor_refresh.supervisor_runs_superseded_code(DEPLOYED_COMMAND)


def test_the_refresh_never_matches_its_own_process(monkeypatch):
class PgrepResult:
stdout = f"{__import__('os').getpid()}\n"

monkeypatch.setattr(
supervisor_refresh.subprocess, "run", lambda *a, **k: PgrepResult()
)

assert supervisor_refresh.find_supervisor_process_ids() == []


def test_the_deployed_command_is_read_from_its_file(tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND + "\n")

assert (
supervisor_refresh.read_deployed_command(str(command_file)) == DEPLOYED_COMMAND
)


def test_main_restarts_only_when_the_running_code_is_superseded(monkeypatch, tmp_path):
command_file = tmp_path / "clawde-service-deployed-command"
command_file.write_text(DEPLOYED_COMMAND)
restart_commands = []
monkeypatch.setattr(
supervisor_refresh,
"parse_arguments",
lambda: type(
"Arguments",
(),
{
"deployed_command_file": str(command_file),
"restart_command": "restart-me",
},
),
)
monkeypatch.setattr(
supervisor_refresh,
"restart_the_supervisor",
lambda command: restart_commands.append(command) or 0,
)

stub_live_supervisors(monkeypatch, [DEPLOYED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == []

stub_live_supervisors(monkeypatch, [SUPERSEDED_COMMAND])
assert supervisor_refresh.main() == 0
assert restart_commands == ["restart-me"]