@misterhuydo/sentinel 1.0.58 → 1.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.cairn/.hint-lock
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-22T14:
|
|
1
|
+
2026-03-22T14:37:46.791Z
|
package/.cairn/minify-map.json
CHANGED
package/.cairn/session.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-22T14:
|
|
3
|
-
"checkpoint_at": "2026-03-22T14:
|
|
2
|
+
"message": "Auto-checkpoint at 2026-03-22T14:43:35.624Z",
|
|
3
|
+
"checkpoint_at": "2026-03-22T14:43:35.625Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { execSync, spawnSync } = require('child_process');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const ok = msg => console.log(chalk.green(' ✔'), msg);
|
|
8
|
+
const info = msg => console.log(chalk.cyan(' →'), msg);
|
|
9
|
+
const warn = msg => console.log(chalk.yellow(' ⚠'), msg);
|
|
10
|
+
module.exports = async function upgrade() {
|
|
11
|
+
const { version: current } = require('../package.json');
|
|
12
|
+
const defaultWorkspace = path.join(os.homedir(), 'sentinel');
|
|
13
|
+
const codeDir = path.join(defaultWorkspace, 'code');
|
|
14
|
+
if (!fs.existsSync(codeDir)) {
|
|
15
|
+
console.error(chalk.red(' ✖ Sentinel code directory not found at ' + codeDir));
|
|
16
|
+
console.error(' Run: sentinel init');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
info(`Current version: ${current}`);
|
|
20
|
+
info('Installing latest @misterhuydo/sentinel...');
|
|
21
|
+
const install = spawnSync('npm', ['install', '-g', '@misterhuydo/sentinel@latest'], { stdio: 'inherit' });
|
|
22
|
+
if (install.status !== 0) {
|
|
23
|
+
console.error(chalk.red(' ✖ npm install failed'));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const npmRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
27
|
+
const pkgDir = path.join(npmRoot, '@misterhuydo', 'sentinel');
|
|
28
|
+
const src = path.join(pkgDir, 'python');
|
|
29
|
+
if (!fs.existsSync(src)) {
|
|
30
|
+
console.error(chalk.red(' ✖ Bundled Python source not found in installed package'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
info('Deploying Python source...');
|
|
34
|
+
fs.copySync(src, codeDir, { overwrite: true });
|
|
35
|
+
const scriptsDir = path.join(codeDir, 'scripts');
|
|
36
|
+
if (fs.existsSync(scriptsDir)) {
|
|
37
|
+
const shFiles = fs.readdirSync(scriptsDir)
|
|
38
|
+
.filter(f => f.endsWith('.sh'))
|
|
39
|
+
.map(f => path.join(scriptsDir, f));
|
|
40
|
+
if (shFiles.length) spawnSync('chmod', ['+x', ...shFiles], { stdio: 'inherit' });
|
|
41
|
+
}
|
|
42
|
+
ok('Python source updated');
|
|
43
|
+
const { version: latest } = require(path.join(pkgDir, 'package.json'));
|
|
44
|
+
ok(`Upgraded: ${current} → ${latest}`);
|
|
45
|
+
const startAll = path.join(defaultWorkspace, 'startAll.sh');
|
|
46
|
+
const stopAll = path.join(defaultWorkspace, 'stopAll.sh');
|
|
47
|
+
if (fs.existsSync(stopAll) && fs.existsSync(startAll)) {
|
|
48
|
+
info('Restarting Sentinel...');
|
|
49
|
+
spawnSync('bash', [stopAll], { stdio: 'inherit' });
|
|
50
|
+
spawnSync('bash', [startAll], { stdio: 'inherit' });
|
|
51
|
+
ok('Sentinel restarted');
|
|
52
|
+
} else {
|
|
53
|
+
warn('No startAll.sh found — restart manually');
|
|
54
|
+
}
|
|
55
|
+
};
|
package/package.json
CHANGED
|
@@ -875,70 +875,36 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
875
875
|
|
|
876
876
|
if name == "upgrade_sentinel":
|
|
877
877
|
import threading
|
|
878
|
-
import signal as _sig
|
|
879
|
-
|
|
880
|
-
code_dir = Path(__file__).resolve().parent.parent # sentinel repo root
|
|
881
|
-
project_dir = Path(".").resolve()
|
|
882
|
-
steps: list[dict] = []
|
|
883
|
-
|
|
884
|
-
# Step 1: git pull the sentinel agent code
|
|
885
|
-
pull = _git_pull(code_dir)
|
|
886
|
-
steps.append({"step": "git_pull", **pull})
|
|
887
|
-
already_latest = "already up to date" in pull.get("detail", "").lower()
|
|
888
|
-
|
|
889
|
-
if pull["status"] == "error":
|
|
890
|
-
return json.dumps({"status": "error", "steps": steps,
|
|
891
|
-
"note": "git pull failed — check network / SSH key"})
|
|
892
|
-
|
|
893
|
-
# Step 2: pip install (update Python deps)
|
|
894
|
-
venv_pip = code_dir / ".venv" / "bin" / "pip"
|
|
895
|
-
pip_cmd = str(venv_pip) if venv_pip.exists() else "pip3"
|
|
896
|
-
req_file = code_dir / "requirements.txt"
|
|
897
|
-
if req_file.exists():
|
|
898
|
-
try:
|
|
899
|
-
r = subprocess.run(
|
|
900
|
-
[pip_cmd, "install", "-q", "-r", str(req_file)],
|
|
901
|
-
capture_output=True, text=True, timeout=120,
|
|
902
|
-
)
|
|
903
|
-
steps.append({
|
|
904
|
-
"step": "pip_install",
|
|
905
|
-
"status": "ok" if r.returncode == 0 else "warn",
|
|
906
|
-
"detail": (r.stderr or "").strip()[:200],
|
|
907
|
-
})
|
|
908
|
-
except Exception as e:
|
|
909
|
-
steps.append({"step": "pip_install", "status": "warn", "detail": str(e)})
|
|
910
878
|
|
|
911
|
-
|
|
879
|
+
# Sentinel is installed via npm — use `sentinel upgrade` which handles
|
|
880
|
+
# npm install + Python bundle copy + restart via stopAll/startAll.
|
|
881
|
+
# Run it in the background after a short delay so the Slack reply is
|
|
882
|
+
# sent before the process is replaced.
|
|
883
|
+
try:
|
|
884
|
+
r = subprocess.run(
|
|
885
|
+
["sentinel", "--version"],
|
|
886
|
+
capture_output=True, text=True, timeout=10,
|
|
887
|
+
)
|
|
888
|
+
sentinel_bin_ok = r.returncode == 0
|
|
889
|
+
except Exception:
|
|
890
|
+
sentinel_bin_ok = False
|
|
891
|
+
|
|
892
|
+
if not sentinel_bin_ok:
|
|
912
893
|
return json.dumps({
|
|
913
|
-
"status": "
|
|
914
|
-
"
|
|
915
|
-
"note": "Sentinel is already up to date. No restart needed.",
|
|
894
|
+
"status": "error",
|
|
895
|
+
"note": "`sentinel` CLI not found. Run: npm install -g @misterhuydo/sentinel",
|
|
916
896
|
})
|
|
917
897
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
def _restart_scripts():
|
|
923
|
-
import time; time.sleep(10)
|
|
924
|
-
subprocess.Popen(
|
|
925
|
-
f"bash {stop_sh} && sleep 2 && bash {start_sh}",
|
|
926
|
-
shell=True,
|
|
927
|
-
)
|
|
928
|
-
threading.Thread(target=_restart_scripts, daemon=True).start()
|
|
929
|
-
restart_method = "stop.sh + start.sh"
|
|
930
|
-
else:
|
|
931
|
-
# SIGTERM self — systemd (Restart=always) will bring it back up
|
|
932
|
-
# 10s delay gives Claude time to generate + post the reply before we die
|
|
933
|
-
threading.Timer(10.0, lambda: os.kill(os.getpid(), _sig.SIGTERM)).start()
|
|
934
|
-
restart_method = "SIGTERM → systemd restart"
|
|
898
|
+
def _do_upgrade():
|
|
899
|
+
import time
|
|
900
|
+
time.sleep(10) # give Slack time to post the reply
|
|
901
|
+
subprocess.Popen(["sentinel", "upgrade"], close_fds=True)
|
|
935
902
|
|
|
936
|
-
|
|
937
|
-
logger.info("Boss: upgrade_sentinel
|
|
903
|
+
threading.Thread(target=_do_upgrade, daemon=True).start()
|
|
904
|
+
logger.info("Boss: upgrade_sentinel scheduled via `sentinel upgrade`")
|
|
938
905
|
return json.dumps({
|
|
939
906
|
"status": "ok",
|
|
940
|
-
"
|
|
941
|
-
"note": "Upgrade complete. Sentinel is restarting — give it a few seconds then I'll be back.",
|
|
907
|
+
"note": "Upgrade started — pulling latest version via npm and restarting. Give me ~30 seconds then I'll be back.",
|
|
942
908
|
})
|
|
943
909
|
|
|
944
910
|
return json.dumps({"error": f"unknown tool: {name}"})
|
|
@@ -955,6 +921,7 @@ async def _handle_with_cli(
|
|
|
955
921
|
cfg_loader,
|
|
956
922
|
store,
|
|
957
923
|
slack_client=None,
|
|
924
|
+
user_name: str = "",
|
|
958
925
|
) -> tuple[str, bool]:
|
|
959
926
|
"""Fallback: use `claude --print` for users without an Anthropic API key."""
|
|
960
927
|
status_json = await _run_tool("get_status", {"hours": 24}, cfg_loader, store)
|
|
@@ -989,6 +956,7 @@ async def _handle_with_cli(
|
|
|
989
956
|
|
|
990
957
|
prompt = (
|
|
991
958
|
_SYSTEM
|
|
959
|
+
+ (f"\nYou are speaking with: {user_name}" if user_name else "")
|
|
992
960
|
+ f"\n\nCurrent time: {ts}"
|
|
993
961
|
+ f"\nSentinel status: {'⏸ PAUSED' if paused else '▶ RUNNING'}"
|
|
994
962
|
+ f"\nManaged repos: {', '.join(repos) if repos else '(none configured)'}"
|
|
@@ -1050,6 +1018,7 @@ async def handle_message(
|
|
|
1050
1018
|
cfg_loader,
|
|
1051
1019
|
store,
|
|
1052
1020
|
slack_client=None,
|
|
1021
|
+
user_name: str = "",
|
|
1053
1022
|
) -> tuple[str, bool]:
|
|
1054
1023
|
"""
|
|
1055
1024
|
Process one user message through the Sentinel Boss (Claude with tool use).
|
|
@@ -1075,7 +1044,7 @@ async def handle_message(
|
|
|
1075
1044
|
|
|
1076
1045
|
api_key = cfg_loader.sentinel.anthropic_api_key or os.environ.get("ANTHROPIC_API_KEY", "")
|
|
1077
1046
|
if not api_key:
|
|
1078
|
-
return await _handle_with_cli(message, history, cfg_loader, store, slack_client=slack_client)
|
|
1047
|
+
return await _handle_with_cli(message, history, cfg_loader, store, slack_client=slack_client, user_name=user_name)
|
|
1079
1048
|
|
|
1080
1049
|
client = anthropic.Anthropic(api_key=api_key)
|
|
1081
1050
|
|
|
@@ -1086,6 +1055,7 @@ async def handle_message(
|
|
|
1086
1055
|
known_projects = [_read_project_name(d) for d in _find_project_dirs()]
|
|
1087
1056
|
system = (
|
|
1088
1057
|
_SYSTEM
|
|
1058
|
+
+ (f"\nYou are speaking with: {user_name}" if user_name else "")
|
|
1089
1059
|
+ f"\n\nCurrent time: {ts}"
|
|
1090
1060
|
+ f"\nSentinel status: {'⏸ PAUSED' if paused else '▶ RUNNING'}"
|
|
1091
1061
|
+ f"\nManaged repos: {', '.join(repos) if repos else '(none configured)'}"
|
|
@@ -330,6 +330,7 @@ async def _run_turn(session: _Session, message: str, client, cfg_loader, store)
|
|
|
330
330
|
reply, is_done = await handle_message(
|
|
331
331
|
message, session.history, cfg_loader, store,
|
|
332
332
|
slack_client=client,
|
|
333
|
+
user_name=session.user_name,
|
|
333
334
|
)
|
|
334
335
|
except Exception as e:
|
|
335
336
|
logger.exception("Sentinel Boss error: %s", e)
|