@misterhuydo/sentinel 1.5.42 → 1.5.44
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/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.5.
|
|
1
|
+
__version__ = "1.5.44"
|
|
@@ -105,6 +105,7 @@ class LogSourceConfig:
|
|
|
105
105
|
cf_url: str = ""
|
|
106
106
|
cf_token: str = ""
|
|
107
107
|
sync_enabled: bool = True
|
|
108
|
+
health_url: str = "" # optional HTTP health endpoint for this log source's service
|
|
108
109
|
|
|
109
110
|
|
|
110
111
|
@dataclass
|
|
@@ -308,6 +309,7 @@ class ConfigLoader:
|
|
|
308
309
|
s.cf_token = d.get("CF_TOKEN", "")
|
|
309
310
|
s.target_repo = d.get("TARGET_REPO", "auto")
|
|
310
311
|
s.sync_enabled = d.get("SYNC_ENABLED", "true").lower() != "false"
|
|
312
|
+
s.health_url = d.get("HEALTH_URL", "")
|
|
311
313
|
self.log_sources[s.name] = s
|
|
312
314
|
|
|
313
315
|
def _load_repos(self):
|
|
@@ -157,7 +157,7 @@ COMPLETE TOOL REFERENCE
|
|
|
157
157
|
custom fetch, and filter_logs searches them automatically.
|
|
158
158
|
Use grep_filter for INFO-level or feature-specific patterns that the default
|
|
159
159
|
WARN/ERROR filter would miss.
|
|
160
|
-
"fetch logs", "fetch
|
|
160
|
+
"fetch logs", "fetch logs for <source> with filter <pattern>", "fetch without filter"
|
|
161
161
|
|
|
162
162
|
7. search_logs Live SSH grep on production servers using GREP_FILTER.
|
|
163
163
|
Falls back to cached files if SSH unavailable.
|
|
@@ -171,7 +171,11 @@ COMPLETE TOOL REFERENCE
|
|
|
171
171
|
9. tail_log Last N lines of a log source live, no filter.
|
|
172
172
|
"show recent SSOLWA logs", "tail STS", "last 200 lines from 1881"
|
|
173
173
|
|
|
174
|
-
10.
|
|
174
|
+
10. check_health Call HEALTH_URL for a log source or repo — returns live version + status.
|
|
175
|
+
Use whenever deployment status needs verification. NEVER guess from logs.
|
|
176
|
+
"is version X deployed?", "what version is <service> running?", "is the service up?"
|
|
177
|
+
|
|
178
|
+
11. ask_logs Ask Claude Code to read and reason over log history.
|
|
175
179
|
Use for summarisation, pattern detection, trend analysis.
|
|
176
180
|
"what caused 400s in 1881 logs?", "summarise last week of STS logs"
|
|
177
181
|
|
|
@@ -576,9 +580,10 @@ CORRECT:
|
|
|
576
580
|
"No '<feature log pattern>' lines yet — the code path hasn't been triggered since the last
|
|
577
581
|
fetch. This says nothing about whether the release is deployed."
|
|
578
582
|
|
|
579
|
-
To verify if a release is live
|
|
580
|
-
|
|
581
|
-
|
|
583
|
+
To verify if a release is live:
|
|
584
|
+
1. FIRST: call check_health for the relevant source/repo — it returns the live version directly.
|
|
585
|
+
2. If no HEALTH_URL is configured: search_logs with query "Starting|started in|version|initialized".
|
|
586
|
+
NEVER state deployment status without calling check_health first.
|
|
582
587
|
- If a tool call will take a moment (search, fetch, pull), prefix your reply with a brief "working" line ending in "..." before the results, e.g. "Searching SSOLWA for TryDig activity..." then the actual output.
|
|
583
588
|
Never just say a working line and stop — always follow it with the results in the same message.
|
|
584
589
|
|
|
@@ -1443,6 +1448,30 @@ _TOOLS = [
|
|
|
1443
1448
|
"required": ["question"],
|
|
1444
1449
|
},
|
|
1445
1450
|
},
|
|
1451
|
+
{
|
|
1452
|
+
"name": "check_health",
|
|
1453
|
+
"description": (
|
|
1454
|
+
"Call the HEALTH_URL for a log source or repo and return the raw JSON response. "
|
|
1455
|
+
"Use this to verify whether a service is running and what version it reports. "
|
|
1456
|
+
"ALWAYS call this instead of guessing deployment status from log evidence. "
|
|
1457
|
+
"Use for: 'is version X deployed?', 'what version is the service running?', "
|
|
1458
|
+
"'is the service up?', 'verify the release is live'."
|
|
1459
|
+
),
|
|
1460
|
+
"input_schema": {
|
|
1461
|
+
"type": "object",
|
|
1462
|
+
"properties": {
|
|
1463
|
+
"source": {
|
|
1464
|
+
"type": "string",
|
|
1465
|
+
"description": "Log source name (e.g. 'SSOLWA') or repo name to check. "
|
|
1466
|
+
"Checks HEALTH_URL from that source/repo config.",
|
|
1467
|
+
},
|
|
1468
|
+
"url": {
|
|
1469
|
+
"type": "string",
|
|
1470
|
+
"description": "Direct health URL to call (overrides config). Use when the user provides a URL.",
|
|
1471
|
+
},
|
|
1472
|
+
},
|
|
1473
|
+
},
|
|
1474
|
+
},
|
|
1446
1475
|
{
|
|
1447
1476
|
"name": "post_file",
|
|
1448
1477
|
"description": (
|
|
@@ -2040,6 +2069,36 @@ def _filter_log_sources(props_files: list, source_hint: str) -> list:
|
|
|
2040
2069
|
return matched
|
|
2041
2070
|
|
|
2042
2071
|
|
|
2072
|
+
def _auto_health_check(source_hint: str, cfg_loader) -> dict | None:
|
|
2073
|
+
"""
|
|
2074
|
+
If any log source or repo matching source_hint has a HEALTH_URL, call it and return
|
|
2075
|
+
the result so it can be injected into fetch/filter responses automatically.
|
|
2076
|
+
Returns None if no health URL is configured or the call fails silently.
|
|
2077
|
+
"""
|
|
2078
|
+
try:
|
|
2079
|
+
import requests as _req
|
|
2080
|
+
url = ""
|
|
2081
|
+
for src_name, src in cfg_loader.log_sources.items():
|
|
2082
|
+
if source_hint.lower() in src_name.lower() and getattr(src, "health_url", ""):
|
|
2083
|
+
url = src.health_url
|
|
2084
|
+
break
|
|
2085
|
+
if not url:
|
|
2086
|
+
for repo_name, repo in cfg_loader.repos.items():
|
|
2087
|
+
if source_hint.lower() in repo_name.lower() and repo.health_url:
|
|
2088
|
+
url = repo.health_url
|
|
2089
|
+
break
|
|
2090
|
+
if not url:
|
|
2091
|
+
return None
|
|
2092
|
+
resp = _req.get(url, timeout=8)
|
|
2093
|
+
try:
|
|
2094
|
+
data = resp.json()
|
|
2095
|
+
except Exception:
|
|
2096
|
+
data = resp.text[:500]
|
|
2097
|
+
return {"health_url": url, "status_code": resp.status_code, "health": data}
|
|
2098
|
+
except Exception:
|
|
2099
|
+
return None
|
|
2100
|
+
|
|
2101
|
+
|
|
2043
2102
|
# ── Tool execution ────────────────────────────────────────────────────────────
|
|
2044
2103
|
|
|
2045
2104
|
async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=None, user_id: str = "", channel: str = "", is_admin: bool = False) -> str:
|
|
@@ -2900,6 +2959,7 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
2900
2959
|
sources_searched = [d.name + (" [temp]" if is_temp else "") for d, is_temp in search_pairs]
|
|
2901
2960
|
if total == 0:
|
|
2902
2961
|
has_temp = bool(temp_dirs)
|
|
2962
|
+
health = _auto_health_check(source_f, cfg_loader) if source_f else None
|
|
2903
2963
|
return json.dumps({
|
|
2904
2964
|
"query": query_f,
|
|
2905
2965
|
"total_matches": 0,
|
|
@@ -2910,6 +2970,7 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
2910
2970
|
+ "If searching for a specific log line from a new feature, use fetch_logs "
|
|
2911
2971
|
"with a matching grep_filter first — the default filter only captures WARN/ERROR."
|
|
2912
2972
|
),
|
|
2973
|
+
**({"service_health": health} if health else {}),
|
|
2913
2974
|
})
|
|
2914
2975
|
|
|
2915
2976
|
# Pattern grouping: count occurrences of each error signature
|
|
@@ -2951,6 +3012,7 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
2951
3012
|
except Exception:
|
|
2952
3013
|
pass
|
|
2953
3014
|
|
|
3015
|
+
health = _auto_health_check(source_f, cfg_loader) if source_f else None
|
|
2954
3016
|
return json.dumps({
|
|
2955
3017
|
"query": query_f,
|
|
2956
3018
|
"total_matches": total,
|
|
@@ -2960,6 +3022,7 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
2960
3022
|
"sample_lines": sample_lines,
|
|
2961
3023
|
"time_span": time_span,
|
|
2962
3024
|
"capped": total >= max_matches,
|
|
3025
|
+
**({"service_health": health} if health else {}),
|
|
2963
3026
|
})
|
|
2964
3027
|
|
|
2965
3028
|
if name == "trigger_poll":
|
|
@@ -3112,7 +3175,12 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
3112
3175
|
except Exception as e:
|
|
3113
3176
|
results.append({"source": props.stem, "error": str(e)})
|
|
3114
3177
|
|
|
3115
|
-
|
|
3178
|
+
health = _auto_health_check(source_filter, cfg_loader) if source_filter else None
|
|
3179
|
+
return json.dumps({
|
|
3180
|
+
"fetched": len(results),
|
|
3181
|
+
"results": results,
|
|
3182
|
+
**({"service_health": health} if health else {}),
|
|
3183
|
+
})
|
|
3116
3184
|
|
|
3117
3185
|
if name == "watch_bot":
|
|
3118
3186
|
if not is_admin:
|
|
@@ -3552,6 +3620,50 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
3552
3620
|
results.append({"project": d.name, "status": "error", "detail": str(e)})
|
|
3553
3621
|
return json.dumps({"results": results})
|
|
3554
3622
|
|
|
3623
|
+
if name == "check_health":
|
|
3624
|
+
import requests as _requests
|
|
3625
|
+
url = inputs.get("url", "").strip()
|
|
3626
|
+
source_hint = inputs.get("source", "").strip().lower()
|
|
3627
|
+
|
|
3628
|
+
# Resolve URL from config if not provided directly
|
|
3629
|
+
if not url:
|
|
3630
|
+
# Check log sources first, then repos
|
|
3631
|
+
for src_name, src in cfg_loader.log_sources.items():
|
|
3632
|
+
if source_hint in src_name.lower() and getattr(src, "health_url", ""):
|
|
3633
|
+
url = src.health_url
|
|
3634
|
+
break
|
|
3635
|
+
if not url:
|
|
3636
|
+
for repo_name, repo in cfg_loader.repos.items():
|
|
3637
|
+
if source_hint in repo_name.lower() and repo.health_url:
|
|
3638
|
+
url = repo.health_url
|
|
3639
|
+
break
|
|
3640
|
+
|
|
3641
|
+
if not url:
|
|
3642
|
+
configured = {
|
|
3643
|
+
**{n: s.health_url for n, s in cfg_loader.log_sources.items() if getattr(s, "health_url", "")},
|
|
3644
|
+
**{n: r.health_url for n, r in cfg_loader.repos.items() if r.health_url},
|
|
3645
|
+
}
|
|
3646
|
+
return json.dumps({
|
|
3647
|
+
"error": f"No HEALTH_URL found for '{source_hint}'.",
|
|
3648
|
+
"configured_health_urls": configured or "none",
|
|
3649
|
+
"hint": "Add HEALTH_URL=https://... to the log-source or repo .properties file, "
|
|
3650
|
+
"or pass url= directly.",
|
|
3651
|
+
})
|
|
3652
|
+
|
|
3653
|
+
try:
|
|
3654
|
+
resp = _requests.get(url, timeout=10)
|
|
3655
|
+
try:
|
|
3656
|
+
data = resp.json()
|
|
3657
|
+
except Exception:
|
|
3658
|
+
data = resp.text[:2000]
|
|
3659
|
+
return json.dumps({
|
|
3660
|
+
"url": url,
|
|
3661
|
+
"status_code": resp.status_code,
|
|
3662
|
+
"response": data,
|
|
3663
|
+
})
|
|
3664
|
+
except Exception as e:
|
|
3665
|
+
return json.dumps({"url": url, "error": str(e)})
|
|
3666
|
+
|
|
3555
3667
|
if name == "tail_log":
|
|
3556
3668
|
source = inputs.get("source", "").lower()
|
|
3557
3669
|
lines = int(inputs.get("lines", 100))
|