@misterhuydo/sentinel 1.5.32 → 1.5.34
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.34"
|
|
@@ -1181,6 +1181,15 @@ _TOOLS = [
|
|
|
1181
1181
|
"type": "string",
|
|
1182
1182
|
"description": "Project short name this bot's issues should be routed to (e.g. '1881', 'elprint'). Infer from context or ask user before calling.",
|
|
1183
1183
|
},
|
|
1184
|
+
"target_repo": {
|
|
1185
|
+
"type": "string",
|
|
1186
|
+
"description": (
|
|
1187
|
+
"Optional. Repo name to route this bot's issues to (e.g. '1881-SSOLoginWebApp'). "
|
|
1188
|
+
"Required when the project has multiple repos — without it, routing will fail. "
|
|
1189
|
+
"If the project has only one repo, omit this and it will be auto-selected. "
|
|
1190
|
+
"Ask the user which repo if unclear."
|
|
1191
|
+
),
|
|
1192
|
+
},
|
|
1184
1193
|
},
|
|
1185
1194
|
"required": ["user_ids"],
|
|
1186
1195
|
},
|
|
@@ -3095,6 +3104,18 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
3095
3104
|
"action_needed": "Ask the user to specify the project, then retry with project filled in.",
|
|
3096
3105
|
})
|
|
3097
3106
|
|
|
3107
|
+
target_repo_arg = (inputs.get("target_repo") or "").strip()
|
|
3108
|
+
# Fuzzy-match target_repo against known repos
|
|
3109
|
+
resolved_target_repo = ""
|
|
3110
|
+
if target_repo_arg and hasattr(cfg_loader, "repos"):
|
|
3111
|
+
if target_repo_arg in cfg_loader.repos:
|
|
3112
|
+
resolved_target_repo = target_repo_arg
|
|
3113
|
+
else:
|
|
3114
|
+
for rname in cfg_loader.repos:
|
|
3115
|
+
if target_repo_arg.lower() in rname.lower() or rname.lower() in target_repo_arg.lower():
|
|
3116
|
+
resolved_target_repo = rname
|
|
3117
|
+
break
|
|
3118
|
+
|
|
3098
3119
|
results = []
|
|
3099
3120
|
for uid in user_ids:
|
|
3100
3121
|
if not slack_client:
|
|
@@ -3107,9 +3128,9 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
3107
3128
|
results.append({"user_id": uid, "status": "skipped", "reason": "not a bot — only bots can be watched passively"})
|
|
3108
3129
|
continue
|
|
3109
3130
|
bot_name = user.get("real_name") or user.get("name") or uid
|
|
3110
|
-
store.add_watched_bot(uid, bot_name, added_by="boss", project_name=resolved_project)
|
|
3111
|
-
logger.info("Boss: now watching bot %s (%s) → project '%s'", bot_name, uid, resolved_project or "unset")
|
|
3112
|
-
results.append({"user_id": uid, "bot_name": bot_name, "project": resolved_project, "status": "watching"})
|
|
3131
|
+
store.add_watched_bot(uid, bot_name, added_by="boss", project_name=resolved_project, target_repo=resolved_target_repo)
|
|
3132
|
+
logger.info("Boss: now watching bot %s (%s) → project '%s', repo '%s'", bot_name, uid, resolved_project or "unset", resolved_target_repo or "auto")
|
|
3133
|
+
results.append({"user_id": uid, "bot_name": bot_name, "project": resolved_project, "target_repo": resolved_target_repo or "auto", "status": "watching"})
|
|
3113
3134
|
except Exception as e:
|
|
3114
3135
|
results.append({"user_id": uid, "status": "error", "reason": str(e)})
|
|
3115
3136
|
return json.dumps({"results": results})
|
|
@@ -181,6 +181,28 @@ 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) -> str:
|
|
185
|
+
"""
|
|
186
|
+
Try to infer a target repo from bot message content.
|
|
187
|
+
Looks for 'Service: <name>' or 'service: <name>' patterns and fuzzy-matches
|
|
188
|
+
the extracted name against known repo names. Returns the repo name on a unique
|
|
189
|
+
match, empty string if ambiguous or no match found.
|
|
190
|
+
"""
|
|
191
|
+
import re as _re
|
|
192
|
+
# Extract service hints: "Service: STS", "service: SSOLWA", etc.
|
|
193
|
+
hints = _re.findall(r'(?i)service:\s*(\S+)', text)
|
|
194
|
+
# Also try class names: "Class: SomeService" — extract first word segment
|
|
195
|
+
hints += _re.findall(r'(?i)class:\s*(\w+)', text)
|
|
196
|
+
|
|
197
|
+
for hint in hints:
|
|
198
|
+
hint_lower = hint.lower().rstrip(".,:")
|
|
199
|
+
matches = [r for r in repos if hint_lower in r.lower()]
|
|
200
|
+
if len(matches) == 1:
|
|
201
|
+
return matches[0]
|
|
202
|
+
|
|
203
|
+
return ""
|
|
204
|
+
|
|
205
|
+
|
|
184
206
|
async def _handle_bot_message(event: dict, client, cfg_loader, store) -> None:
|
|
185
207
|
"""
|
|
186
208
|
Called when a watched bot posts a message.
|
|
@@ -214,6 +236,27 @@ async def _handle_bot_message(event: dict, client, cfg_loader, store) -> None:
|
|
|
214
236
|
None,
|
|
215
237
|
)
|
|
216
238
|
project_name = (bot_info or {}).get("project_name") or ""
|
|
239
|
+
target_repo = (bot_info or {}).get("target_repo") or ""
|
|
240
|
+
|
|
241
|
+
# Auto-detect target_repo from message content when not pre-configured
|
|
242
|
+
if not target_repo and hasattr(cfg_loader, "repos") and cfg_loader.repos:
|
|
243
|
+
target_repo = _infer_target_repo(text, cfg_loader.repos)
|
|
244
|
+
if not target_repo and len(cfg_loader.repos) > 1:
|
|
245
|
+
# Can't determine — ask in the channel where the bot posted
|
|
246
|
+
bot_name = (bot_info or {}).get("bot_name") or bot_id
|
|
247
|
+
repo_names = ", ".join(f"`{r}`" for r in sorted(cfg_loader.repos))
|
|
248
|
+
try:
|
|
249
|
+
await client.chat_postMessage(
|
|
250
|
+
channel=channel,
|
|
251
|
+
text=(
|
|
252
|
+
f":mag: I got an alert from *{bot_name}* but can't tell which repo to route it to.\n"
|
|
253
|
+
f"Available repos: {repo_names}\n"
|
|
254
|
+
f"Re-register with: `@Sentinel watch @{bot_name} for <repo-name>`"
|
|
255
|
+
),
|
|
256
|
+
)
|
|
257
|
+
except Exception as _e:
|
|
258
|
+
logger.warning("Bot watcher: could not post routing question: %s", _e)
|
|
259
|
+
return # drop this message — don't queue an unroutable issue
|
|
217
260
|
|
|
218
261
|
# Resolve the project issues directory
|
|
219
262
|
workspace = _Path(cfg_loader.sentinel.workspace_dir).parent
|
|
@@ -238,9 +281,11 @@ async def _handle_bot_message(event: dict, client, cfg_loader, store) -> None:
|
|
|
238
281
|
|
|
239
282
|
uid = _uuid.uuid4().hex[:8]
|
|
240
283
|
fname = f"bot-{project_name or 'unknown'}-{uid}.txt"
|
|
284
|
+
target_line = f"TARGET_REPO: {target_repo}\n" if target_repo else ""
|
|
241
285
|
content = (
|
|
242
286
|
f"SOURCE: Slack bot {bot_id} in channel {channel}\n"
|
|
243
|
-
f"SLACK_TS: {ts}\n
|
|
287
|
+
f"SLACK_TS: {ts}\n"
|
|
288
|
+
f"{target_line}\n"
|
|
244
289
|
f"{text}"
|
|
245
290
|
)
|
|
246
291
|
(issues_dir / fname).write_text(content, encoding="utf-8")
|
|
@@ -99,6 +99,7 @@ class StateStore:
|
|
|
99
99
|
("add_fix_outcome", "ALTER TABLE fixes ADD COLUMN fix_outcome TEXT"),
|
|
100
100
|
("add_marker_seen_at", "ALTER TABLE fixes ADD COLUMN marker_seen_at TEXT"),
|
|
101
101
|
("add_watched_bots_project", "ALTER TABLE watched_bots ADD COLUMN project_name TEXT"),
|
|
102
|
+
("add_watched_bots_target_repo", "ALTER TABLE watched_bots ADD COLUMN target_repo TEXT"),
|
|
102
103
|
("add_alert_thread_ts", "ALTER TABLE errors ADD COLUMN alert_thread_ts TEXT"),
|
|
103
104
|
("add_alert_channel", "ALTER TABLE errors ADD COLUMN alert_channel TEXT"),
|
|
104
105
|
# knowledge_cache was originally created with a 'cached_at' column; rebuilt with 'expires_at'
|
|
@@ -408,13 +409,13 @@ class StateStore:
|
|
|
408
409
|
"(bot_id TEXT PRIMARY KEY, bot_name TEXT, added_by TEXT, added_at TEXT)"
|
|
409
410
|
)
|
|
410
411
|
|
|
411
|
-
def add_watched_bot(self, bot_id: str, bot_name: str, added_by: str = "config", project_name: str = ""):
|
|
412
|
+
def add_watched_bot(self, bot_id: str, bot_name: str, added_by: str = "config", project_name: str = "", target_repo: str = ""):
|
|
412
413
|
with self._conn() as conn:
|
|
413
414
|
self._ensure_watched_bots_table(conn)
|
|
414
415
|
conn.execute(
|
|
415
|
-
"INSERT OR REPLACE INTO watched_bots (bot_id, bot_name, added_by, added_at, project_name) "
|
|
416
|
-
"VALUES (?, ?, ?, ?, ?)",
|
|
417
|
-
(bot_id, bot_name, added_by, _now(), project_name or None),
|
|
416
|
+
"INSERT OR REPLACE INTO watched_bots (bot_id, bot_name, added_by, added_at, project_name, target_repo) "
|
|
417
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
418
|
+
(bot_id, bot_name, added_by, _now(), project_name or None, target_repo or None),
|
|
418
419
|
)
|
|
419
420
|
|
|
420
421
|
def remove_watched_bot(self, bot_id: str) -> bool:
|