@misterhuydo/sentinel 1.4.49 → 1.4.50
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/session.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-25T11:
|
|
3
|
-
"checkpoint_at": "2026-03-25T11:
|
|
2
|
+
"message": "Auto-checkpoint at 2026-03-25T11:24:19.838Z",
|
|
3
|
+
"checkpoint_at": "2026-03-25T11:24:19.839Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/package.json
CHANGED
|
@@ -21,6 +21,13 @@ logger = logging.getLogger(__name__)
|
|
|
21
21
|
|
|
22
22
|
GIT_TIMEOUT = 60
|
|
23
23
|
|
|
24
|
+
|
|
25
|
+
class MissingToolError(Exception):
|
|
26
|
+
"""Raised when a required build tool (e.g. mvn, gradle) is not installed."""
|
|
27
|
+
def __init__(self, tool: str):
|
|
28
|
+
super().__init__(f"Build tool '{tool}' not found on this server")
|
|
29
|
+
self.tool = tool
|
|
30
|
+
|
|
24
31
|
# Files that must never be modified by Sentinel
|
|
25
32
|
_PROTECTED_PATHS = {".github/", "Jenkinsfile", "pom.xml"}
|
|
26
33
|
|
|
@@ -127,8 +134,7 @@ def _run_tests(repo: RepoConfig, local_path: str) -> bool:
|
|
|
127
134
|
try:
|
|
128
135
|
r = subprocess.run(cmd, cwd=local_path, capture_output=True, text=True, timeout=300)
|
|
129
136
|
except FileNotFoundError:
|
|
130
|
-
|
|
131
|
-
return True
|
|
137
|
+
raise MissingToolError(cmd[0])
|
|
132
138
|
if r.returncode != 0:
|
|
133
139
|
logger.error("Tests failed:\n%s", r.stdout[-2000:] + r.stderr[-1000:])
|
|
134
140
|
return False
|
package/python/sentinel/main.py
CHANGED
|
@@ -22,14 +22,14 @@ from pathlib import Path
|
|
|
22
22
|
from .cairn_client import ensure_installed as cairn_installed, index_repo
|
|
23
23
|
from .config_loader import ConfigLoader, SentinelConfig
|
|
24
24
|
from .fix_engine import generate_fix
|
|
25
|
-
from .git_manager import apply_and_commit, publish, _git_env
|
|
25
|
+
from .git_manager import apply_and_commit, publish, _git_env, MissingToolError
|
|
26
26
|
from .cicd_trigger import trigger as cicd_trigger
|
|
27
27
|
from .log_fetcher import fetch_all
|
|
28
28
|
from .log_parser import parse_all, scan_all_for_markers, ErrorEvent
|
|
29
29
|
from .issue_watcher import scan_issues, mark_done, IssueEvent
|
|
30
30
|
from .repo_router import route
|
|
31
31
|
from .reporter import build_and_send, send_fix_notification, send_failure_notification, send_confirmed_notification, send_regression_notification, send_startup_notification, send_upgrade_notification
|
|
32
|
-
from .notify import notify_fix_blocked, notify_fix_applied
|
|
32
|
+
from .notify import notify_fix_blocked, notify_fix_applied, notify_missing_tool
|
|
33
33
|
from .health_checker import evaluate_repos
|
|
34
34
|
from .state_store import StateStore
|
|
35
35
|
|
|
@@ -106,7 +106,13 @@ async def _handle_error(event: ErrorEvent, cfg_loader: ConfigLoader, store: Stat
|
|
|
106
106
|
})
|
|
107
107
|
return
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
try:
|
|
110
|
+
commit_status, commit_hash = apply_and_commit(event, patch_path, repo, sentinel)
|
|
111
|
+
except MissingToolError as e:
|
|
112
|
+
logger.warning("Missing tool for %s: %s — notifying admin", event.source, e)
|
|
113
|
+
notify_missing_tool(sentinel, e.tool, repo.repo_name, event.source, "")
|
|
114
|
+
store.record_fix(event.fingerprint, "failed", repo_name=repo.repo_name)
|
|
115
|
+
return
|
|
110
116
|
if commit_status != "committed":
|
|
111
117
|
store.record_fix(event.fingerprint, "failed", repo_name=repo.repo_name)
|
|
112
118
|
send_failure_notification(sentinel, {
|
|
@@ -244,6 +250,14 @@ async def _handle_issue(event: IssueEvent, cfg_loader: ConfigLoader, store: Stat
|
|
|
244
250
|
if repo.auto_publish:
|
|
245
251
|
cicd_trigger(repo, store, event.fingerprint)
|
|
246
252
|
|
|
253
|
+
except MissingToolError as e:
|
|
254
|
+
logger.warning("Missing tool for %s: %s — notifying admin", event.source, e)
|
|
255
|
+
submitter_uid = getattr(event, "submitter_user_id", "")
|
|
256
|
+
notify_missing_tool(sentinel, e.tool, repo.repo_name, event.source, submitter_uid)
|
|
257
|
+
# Archive the issue — admin can re-raise it after installing the tool
|
|
258
|
+
store.record_fix(event.fingerprint, "failed", repo_name=repo.repo_name)
|
|
259
|
+
mark_done(event.issue_file)
|
|
260
|
+
|
|
247
261
|
except Exception:
|
|
248
262
|
logger.exception("Unexpected error processing issue %s — archiving to prevent retry loop", event.source)
|
|
249
263
|
store.record_fix(event.fingerprint, "failed", repo_name=repo.repo_name)
|
|
@@ -230,6 +230,34 @@ def notify_fix_blocked(
|
|
|
230
230
|
logger.warning("notify_fix_blocked: email notification failed: %s", exc)
|
|
231
231
|
|
|
232
232
|
|
|
233
|
+
def notify_missing_tool(
|
|
234
|
+
cfg,
|
|
235
|
+
tool: str,
|
|
236
|
+
repo_name: str,
|
|
237
|
+
source: str,
|
|
238
|
+
submitter_user_id: str = "",
|
|
239
|
+
) -> None:
|
|
240
|
+
"""
|
|
241
|
+
Notify admins that a build tool is missing on this server.
|
|
242
|
+
Prompts them to ask Boss to install it.
|
|
243
|
+
"""
|
|
244
|
+
repo_line = f" for *{repo_name}*" if repo_name else ""
|
|
245
|
+
slack_text = (
|
|
246
|
+
f":wrench: *Build tool missing{repo_line}*\n"
|
|
247
|
+
f"*Source:* {source}\n"
|
|
248
|
+
f"The fix was generated but tests couldn't run because `{tool}` is not installed on this server.\n\n"
|
|
249
|
+
f"Ask me to install it:\n"
|
|
250
|
+
f"> @Sentinel install {tool}\n\n"
|
|
251
|
+
f"Once installed, re-raise the issue to apply the fix."
|
|
252
|
+
)
|
|
253
|
+
if submitter_user_id:
|
|
254
|
+
if getattr(cfg, "slack_dm_submitter", True):
|
|
255
|
+
slack_dm(cfg.slack_bot_token, submitter_user_id, slack_text)
|
|
256
|
+
slack_alert(cfg.slack_bot_token, cfg.slack_channel, f"<@{submitter_user_id}> {slack_text}")
|
|
257
|
+
else:
|
|
258
|
+
slack_alert(cfg.slack_bot_token, cfg.slack_channel, f"<!channel> {slack_text}")
|
|
259
|
+
|
|
260
|
+
|
|
233
261
|
def notify_fix_applied(
|
|
234
262
|
cfg,
|
|
235
263
|
source: str,
|
|
@@ -931,6 +931,25 @@ _TOOLS = [
|
|
|
931
931
|
"required": ["repo_name"],
|
|
932
932
|
},
|
|
933
933
|
},
|
|
934
|
+
{
|
|
935
|
+
"name": "install_tool",
|
|
936
|
+
"description": (
|
|
937
|
+
"ADMIN ONLY. Install a missing build tool (e.g. maven, gradle, node) on this server "
|
|
938
|
+
"using Claude Code with shell execution rights. "
|
|
939
|
+
"Use after Sentinel reports a missing tool error. "
|
|
940
|
+
"e.g. 'install maven', 'install gradle', '@Sentinel install mvn'"
|
|
941
|
+
),
|
|
942
|
+
"input_schema": {
|
|
943
|
+
"type": "object",
|
|
944
|
+
"properties": {
|
|
945
|
+
"tool_name": {
|
|
946
|
+
"type": "string",
|
|
947
|
+
"description": "The tool to install, e.g. 'maven', 'gradle', 'node'",
|
|
948
|
+
},
|
|
949
|
+
},
|
|
950
|
+
"required": ["tool_name"],
|
|
951
|
+
},
|
|
952
|
+
},
|
|
934
953
|
{
|
|
935
954
|
"name": "set_maintenance",
|
|
936
955
|
"description": (
|
|
@@ -2193,7 +2212,7 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
2193
2212
|
return json.dumps({"error": "cannot determine user — not clearing"})
|
|
2194
2213
|
|
|
2195
2214
|
# ── Admin-only tools ──────────────────────────────────────────────────────
|
|
2196
|
-
_ADMIN_TOOLS = {"list_all_users", "clear_user_history", "reset_fingerprint", "list_all_errors", "export_db", "merge_pr"}
|
|
2215
|
+
_ADMIN_TOOLS = {"list_all_users", "clear_user_history", "reset_fingerprint", "list_all_errors", "export_db", "merge_pr", "install_tool"}
|
|
2197
2216
|
if name in _ADMIN_TOOLS:
|
|
2198
2217
|
if not is_admin:
|
|
2199
2218
|
return json.dumps({"error": "Admin access required. You are not in SLACK_ADMIN_USERS."})
|
|
@@ -2380,6 +2399,43 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
2380
2399
|
return json.dumps({"status": "error", "pr": pr_url,
|
|
2381
2400
|
"error": f"GitHub API returned {merge_resp.status_code}: {merge_resp.text[:300]}"})
|
|
2382
2401
|
|
|
2402
|
+
if name == "install_tool":
|
|
2403
|
+
import subprocess as _sp
|
|
2404
|
+
tool_name = inputs.get("tool_name", "").strip()
|
|
2405
|
+
if not tool_name:
|
|
2406
|
+
return json.dumps({"error": "tool_name is required"})
|
|
2407
|
+
|
|
2408
|
+
bin_path = cfg_loader.sentinel.claude_code_bin
|
|
2409
|
+
api_key = cfg_loader.sentinel.anthropic_api_key
|
|
2410
|
+
env = {**_os.environ}
|
|
2411
|
+
if api_key:
|
|
2412
|
+
env["ANTHROPIC_API_KEY"] = api_key
|
|
2413
|
+
|
|
2414
|
+
prompt = (
|
|
2415
|
+
f"Install '{tool_name}' on this server so it can be used as a build/test tool. "
|
|
2416
|
+
f"Detect the OS and package manager (yum/dnf/apt), then run the appropriate install command. "
|
|
2417
|
+
f"After installing, verify the installation by running '{tool_name} --version' or the equivalent. "
|
|
2418
|
+
f"Report the installed version. Do not explain — just install and report."
|
|
2419
|
+
)
|
|
2420
|
+
logger.info("Boss install_tool: installing '%s' via Claude Code", tool_name)
|
|
2421
|
+
try:
|
|
2422
|
+
result = _sp.run(
|
|
2423
|
+
[bin_path, "--dangerously-skip-permissions", "--bare", "--print", prompt],
|
|
2424
|
+
capture_output=True, text=True, timeout=300, env=env,
|
|
2425
|
+
)
|
|
2426
|
+
output = ((result.stdout or "") + (result.stderr or "")).strip()
|
|
2427
|
+
success = result.returncode == 0
|
|
2428
|
+
logger.info("Boss install_tool: '%s' install %s:\n%s", tool_name, "OK" if success else "FAILED", output[-500:])
|
|
2429
|
+
return json.dumps({
|
|
2430
|
+
"status": "installed" if success else "failed",
|
|
2431
|
+
"tool": tool_name,
|
|
2432
|
+
"output": output[-1000:],
|
|
2433
|
+
})
|
|
2434
|
+
except FileNotFoundError:
|
|
2435
|
+
return json.dumps({"error": f"Claude Code binary not found at '{bin_path}'"})
|
|
2436
|
+
except _sp.TimeoutExpired:
|
|
2437
|
+
return json.dumps({"error": f"Install timed out for '{tool_name}'"})
|
|
2438
|
+
|
|
2383
2439
|
return json.dumps({"error": f"unknown tool: {name}"})
|
|
2384
2440
|
|
|
2385
2441
|
|