@misterhuydo/sentinel 1.5.51 → 1.5.52
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.52"
|
|
@@ -5192,6 +5192,21 @@ async def _handle_with_api(
|
|
|
5192
5192
|
else:
|
|
5193
5193
|
user_time_hint = ""
|
|
5194
5194
|
_known_projects = [_read_project_name(d) for d in _find_project_dirs()]
|
|
5195
|
+
|
|
5196
|
+
# Build alias map: repo → [alias, alias, ...] from config + DB
|
|
5197
|
+
_alias_lines: list[str] = []
|
|
5198
|
+
for _rname, _rcfg in cfg_loader.repos.items():
|
|
5199
|
+
_aliases = list(getattr(_rcfg, "service_aliases", []))
|
|
5200
|
+
# also pull DB-stored aliases for this repo
|
|
5201
|
+
_db_aliases = [r["service_name"] for r in (store.get_all_service_aliases() if hasattr(store, "get_all_service_aliases") else []) if r.get("repo_name") == _rname]
|
|
5202
|
+
_all = list(dict.fromkeys(_aliases + _db_aliases)) # dedupe, preserve order
|
|
5203
|
+
if _all:
|
|
5204
|
+
_alias_lines.append(f" {_rname} = {', '.join(_all)}")
|
|
5205
|
+
_aliases_block = (
|
|
5206
|
+
"\nService alias map (short names → repo):\n" + "\n".join(_alias_lines)
|
|
5207
|
+
if _alias_lines else ""
|
|
5208
|
+
)
|
|
5209
|
+
|
|
5195
5210
|
system = (
|
|
5196
5211
|
_resolve_system(
|
|
5197
5212
|
boss_mode=getattr(cfg_loader.sentinel, "boss_mode", "standard"),
|
|
@@ -5207,6 +5222,12 @@ async def _handle_with_api(
|
|
|
5207
5222
|
+ (f"\n{user_time_hint}" if user_time_hint else "")
|
|
5208
5223
|
+ f"\nSentinel status: {'⏸ PAUSED' if paused else '▶ RUNNING'}"
|
|
5209
5224
|
+ f"\nManaged repos: {', '.join(repos) if repos else '(none configured)'}"
|
|
5225
|
+
+ _aliases_block
|
|
5226
|
+
+ "\nUNKNOWN SERVICE NAME RULE: If the user mentions a service/repo shorthand that is NOT"
|
|
5227
|
+
+ " in 'Managed repos' and NOT in the alias map above, ask EXACTLY: \"What is <name>?\""
|
|
5228
|
+
+ " before doing anything else. When the user answers with a repo name, immediately call"
|
|
5229
|
+
+ " save_service_alias(service_name=<name>, repo_name=<answer>), then proceed with the"
|
|
5230
|
+
+ " original request. Never guess or assume — always ask and save."
|
|
5210
5231
|
+ (f"\nLog sources: {', '.join(log_sources)}" if log_sources else "")
|
|
5211
5232
|
+ (f"\nKnown projects in workspace: {', '.join(known_projects)}" if known_projects else "")
|
|
5212
5233
|
+ f"\nAdmin access for this user: {'YES — admin tools are available' if is_admin else 'NO — admin tools will be refused'}"
|
|
@@ -181,16 +181,33 @@ import uuid as _uuid
|
|
|
181
181
|
from pathlib import Path as _Path
|
|
182
182
|
|
|
183
183
|
|
|
184
|
-
def _infer_target_repo(text: str, repos: dict, store=None) -> str:
|
|
184
|
+
def _infer_target_repo(text: str, repos: dict, store=None, bot_name: str = "") -> str:
|
|
185
185
|
"""
|
|
186
186
|
Try to infer a target repo from bot message content.
|
|
187
|
-
|
|
188
|
-
1.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
Strategy:
|
|
188
|
+
1. Full-text scan: check every repo's SERVICE_ALIASES as whole-word matches in text.
|
|
189
|
+
If exactly one repo matches → use it. Avoids needing a specific "Service: <name>" pattern.
|
|
190
|
+
2. Structured hints (Service:/Class: patterns) → config aliases → DB aliases → substring.
|
|
191
|
+
3. DB aliases keyed on bot_name (for bots registered via save_service_alias).
|
|
192
|
+
Returns the repo name on an unambiguous match, empty string otherwise.
|
|
192
193
|
"""
|
|
193
194
|
import re as _re
|
|
195
|
+
|
|
196
|
+
text_lower = text.lower()
|
|
197
|
+
|
|
198
|
+
# 1. Full-text scan of all config-declared SERVICE_ALIASES
|
|
199
|
+
matched_repos: set[str] = set()
|
|
200
|
+
for repo_name, repo in repos.items():
|
|
201
|
+
declared = getattr(repo, "service_aliases", [])
|
|
202
|
+
for alias in declared:
|
|
203
|
+
# whole-word match (handles "STS", "SSOLWA", "SecurityTokenService", etc.)
|
|
204
|
+
if _re.search(r'(?i)\b' + _re.escape(alias) + r'\b', text):
|
|
205
|
+
matched_repos.add(repo_name)
|
|
206
|
+
break
|
|
207
|
+
if len(matched_repos) == 1:
|
|
208
|
+
return next(iter(matched_repos))
|
|
209
|
+
|
|
210
|
+
# 2. Structured hints: "Service: X" or "Class: X" patterns
|
|
194
211
|
hints = _re.findall(r'(?i)service:\s*(\S+)', text)
|
|
195
212
|
hints += _re.findall(r'(?i)class:\s*(\w+)', text)
|
|
196
213
|
|
|
@@ -198,23 +215,29 @@ def _infer_target_repo(text: str, repos: dict, store=None) -> str:
|
|
|
198
215
|
hint_clean = hint.rstrip(".,:")
|
|
199
216
|
hint_lower = hint_clean.lower()
|
|
200
217
|
|
|
201
|
-
#
|
|
218
|
+
# Config aliases (already checked above but try exact match here too)
|
|
202
219
|
for repo_name, repo in repos.items():
|
|
203
220
|
declared = getattr(repo, "service_aliases", [])
|
|
204
221
|
if any(hint_lower == a.lower() for a in declared):
|
|
205
222
|
return repo_name
|
|
206
223
|
|
|
207
|
-
#
|
|
224
|
+
# DB aliases
|
|
208
225
|
if store:
|
|
209
226
|
alias = store.get_service_alias(hint_clean)
|
|
210
227
|
if alias and alias in repos:
|
|
211
228
|
return alias
|
|
212
229
|
|
|
213
|
-
#
|
|
230
|
+
# Substring match against repo names
|
|
214
231
|
sub_matches = [r for r in repos if hint_lower in r.lower()]
|
|
215
232
|
if len(sub_matches) == 1:
|
|
216
233
|
return sub_matches[0]
|
|
217
234
|
|
|
235
|
+
# 3. DB alias keyed on bot_name (for bots explicitly mapped via save_service_alias)
|
|
236
|
+
if bot_name and store:
|
|
237
|
+
alias = store.get_service_alias(bot_name)
|
|
238
|
+
if alias and alias in repos:
|
|
239
|
+
return alias
|
|
240
|
+
|
|
218
241
|
return ""
|
|
219
242
|
|
|
220
243
|
|
|
@@ -261,35 +284,36 @@ async def _handle_bot_message(event: dict, client, cfg_loader, store) -> None:
|
|
|
261
284
|
target_repo = (bot_info or {}).get("target_repo") or ""
|
|
262
285
|
|
|
263
286
|
# Auto-detect target_repo from message content when not pre-configured
|
|
287
|
+
bot_name = (bot_info or {}).get("bot_name") or bot_id
|
|
264
288
|
if not target_repo and hasattr(cfg_loader, "repos") and cfg_loader.repos:
|
|
265
|
-
target_repo = _infer_target_repo(text, cfg_loader.repos, store=store)
|
|
289
|
+
target_repo = _infer_target_repo(text, cfg_loader.repos, store=store, bot_name=bot_name)
|
|
266
290
|
if not target_repo and len(cfg_loader.repos) > 1:
|
|
267
291
|
# Can't determine — ask in the channel and store as pending routing
|
|
268
|
-
bot_name = (bot_info or {}).get("bot_name") or bot_id
|
|
269
292
|
service_hint = _extract_service_hint(text)
|
|
270
|
-
|
|
271
|
-
|
|
293
|
+
# Use bot_name as the routing key when no service hint found in message
|
|
294
|
+
routing_key = service_hint or bot_name
|
|
295
|
+
if service_hint:
|
|
296
|
+
question = f":mag: Got an alert from *{bot_name}* — what repo is `{service_hint}`?"
|
|
297
|
+
else:
|
|
298
|
+
preview = text[:120].replace("\n", " ")
|
|
299
|
+
question = f":mag: Got an alert from *{bot_name}* — which repo does this belong to?\n_{preview}_"
|
|
272
300
|
try:
|
|
273
|
-
await client.chat_postMessage(
|
|
274
|
-
channel=channel,
|
|
275
|
-
text=f":mag: Got an alert from *{bot_name}* — what repo is `{service_hint}`?",
|
|
276
|
-
)
|
|
301
|
+
await client.chat_postMessage(channel=channel, text=question)
|
|
277
302
|
except Exception as _e:
|
|
278
303
|
logger.warning("Bot watcher: could not post routing question: %s", _e)
|
|
279
304
|
# Store as pending so Boss can reprocess when user replies
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
logger.warning("Bot watcher: could not store pending routing: %s", _e)
|
|
305
|
+
try:
|
|
306
|
+
default_issues = str(_Path(cfg_loader.sentinel.workspace_dir).parent / "issues")
|
|
307
|
+
store.add_pending_routing(
|
|
308
|
+
service_hint=routing_key,
|
|
309
|
+
bot_id=bot_id,
|
|
310
|
+
channel=channel,
|
|
311
|
+
ts=ts,
|
|
312
|
+
content=text,
|
|
313
|
+
issues_dir=default_issues,
|
|
314
|
+
)
|
|
315
|
+
except Exception as _e:
|
|
316
|
+
logger.warning("Bot watcher: could not store pending routing: %s", _e)
|
|
293
317
|
return # drop this message until routing is resolved
|
|
294
318
|
|
|
295
319
|
# Resolve the project issues directory
|