@misterhuydo/sentinel 1.5.41 → 1.5.43

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.5.41",
3
+ "version": "1.5.43",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -1 +1 @@
1
- __version__ = "1.5.41"
1
+ __version__ = "1.5.43"
@@ -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 SSOLWA with filter provision/phone", "fetch without filter"
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. ask_logs Ask Claude Code to read and reason over log history.
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
 
@@ -568,17 +572,18 @@ WHY: A log line only appears when that exact code path executes. Finding 0 hits
568
572
  lines says nothing about what code version is running. The new line simply hasn't been triggered yet.
569
573
 
570
574
  WRONG — NEVER SAY THESE:
571
- "Zero hits for 'provision/phone called by appId'. Release 6.29.34 has not deployed."
572
- "Found 3 old DEBUG entries but no new INFO line — servers are still on the previous version."
573
- "The new logging code from 6.29.34 is still not deployed."
575
+ "Zero hits for '<feature log pattern>'. Release X.Y.Z has not deployed."
576
+ "Found old log entries but no new ones — servers are still on the previous version."
577
+ "The new code from release X.Y.Z is still not deployed."
574
578
 
575
579
  CORRECT:
576
- "No 'provision/phone called by appId' line yet — the endpoint hasn't logged that path since
577
- the last fetch. This says nothing about whether 6.29.34 is deployed."
580
+ "No '<feature log pattern>' lines yet — the code path hasn't been triggered since the last
581
+ fetch. This says nothing about whether the release is deployed."
578
582
 
579
- To verify if a release is live, offer to search for startup log lines:
580
- search_logs with query "Starting|started in|version|initialized"
581
- NEVER repeat a deployment assertion from a prior turn without doing this check first.
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": (
@@ -3552,6 +3581,50 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
3552
3581
  results.append({"project": d.name, "status": "error", "detail": str(e)})
3553
3582
  return json.dumps({"results": results})
3554
3583
 
3584
+ if name == "check_health":
3585
+ import requests as _requests
3586
+ url = inputs.get("url", "").strip()
3587
+ source_hint = inputs.get("source", "").strip().lower()
3588
+
3589
+ # Resolve URL from config if not provided directly
3590
+ if not url:
3591
+ # Check log sources first, then repos
3592
+ for src_name, src in cfg_loader.log_sources.items():
3593
+ if source_hint in src_name.lower() and getattr(src, "health_url", ""):
3594
+ url = src.health_url
3595
+ break
3596
+ if not url:
3597
+ for repo_name, repo in cfg_loader.repos.items():
3598
+ if source_hint in repo_name.lower() and repo.health_url:
3599
+ url = repo.health_url
3600
+ break
3601
+
3602
+ if not url:
3603
+ configured = {
3604
+ **{n: s.health_url for n, s in cfg_loader.log_sources.items() if getattr(s, "health_url", "")},
3605
+ **{n: r.health_url for n, r in cfg_loader.repos.items() if r.health_url},
3606
+ }
3607
+ return json.dumps({
3608
+ "error": f"No HEALTH_URL found for '{source_hint}'.",
3609
+ "configured_health_urls": configured or "none",
3610
+ "hint": "Add HEALTH_URL=https://... to the log-source or repo .properties file, "
3611
+ "or pass url= directly.",
3612
+ })
3613
+
3614
+ try:
3615
+ resp = _requests.get(url, timeout=10)
3616
+ try:
3617
+ data = resp.json()
3618
+ except Exception:
3619
+ data = resp.text[:2000]
3620
+ return json.dumps({
3621
+ "url": url,
3622
+ "status_code": resp.status_code,
3623
+ "response": data,
3624
+ })
3625
+ except Exception as e:
3626
+ return json.dumps({"url": url, "error": str(e)})
3627
+
3555
3628
  if name == "tail_log":
3556
3629
  source = inputs.get("source", "").lower()
3557
3630
  lines = int(inputs.get("lines", 100))