@misterhuydo/sentinel 1.0.54 → 1.0.56
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/.cairn-project +0 -0
- package/.cairn/.hint-lock +1 -1
- package/.cairn/session.json +2 -2
- package/package.json +1 -1
- package/python/sentinel/config_loader.py +2 -0
- package/python/sentinel/sentinel_boss.py +7 -2
- package/python/sentinel/slack_bot.py +11 -2
- package/templates/sentinel.properties +6 -0
|
File without changes
|
package/.cairn/.hint-lock
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-22T14:06:29.625Z
|
package/.cairn/session.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-
|
|
3
|
-
"checkpoint_at": "2026-03-
|
|
2
|
+
"message": "Auto-checkpoint at 2026-03-22T14:06:41.424Z",
|
|
3
|
+
"checkpoint_at": "2026-03-22T14:06:41.425Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/package.json
CHANGED
|
@@ -61,6 +61,7 @@ class SentinelConfig:
|
|
|
61
61
|
slack_app_token: str = "" # xapp-... (Socket Mode)
|
|
62
62
|
slack_channel: str = "" # optional: restrict to one channel ID or name
|
|
63
63
|
slack_watch_bot_ids: list[str] = field(default_factory=list) # pre-configured bot IDs to watch passively
|
|
64
|
+
slack_allowed_users: list[str] = field(default_factory=list) # if set, only these Slack user IDs can talk to Boss
|
|
64
65
|
project_name: str = "" # optional: friendly name used by Sentinel Boss (e.g. "1881")
|
|
65
66
|
|
|
66
67
|
|
|
@@ -155,6 +156,7 @@ class ConfigLoader:
|
|
|
155
156
|
c.slack_app_token = d.get("SLACK_APP_TOKEN", "")
|
|
156
157
|
c.slack_channel = d.get("SLACK_CHANNEL", "")
|
|
157
158
|
c.slack_watch_bot_ids = _csv(d.get("SLACK_WATCH_BOT_IDS", ""))
|
|
159
|
+
c.slack_allowed_users = _csv(d.get("SLACK_ALLOWED_USERS", ""))
|
|
158
160
|
c.project_name = d.get("PROJECT_NAME", "")
|
|
159
161
|
self.sentinel = c
|
|
160
162
|
|
|
@@ -919,7 +919,7 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
919
919
|
start_sh = project_dir / "start.sh"
|
|
920
920
|
if stop_sh.exists() and start_sh.exists():
|
|
921
921
|
def _restart_scripts():
|
|
922
|
-
import time; time.sleep(
|
|
922
|
+
import time; time.sleep(10)
|
|
923
923
|
subprocess.Popen(
|
|
924
924
|
f"bash {stop_sh} && sleep 2 && bash {start_sh}",
|
|
925
925
|
shell=True,
|
|
@@ -928,7 +928,8 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
928
928
|
restart_method = "stop.sh + start.sh"
|
|
929
929
|
else:
|
|
930
930
|
# SIGTERM self — systemd (Restart=always) will bring it back up
|
|
931
|
-
|
|
931
|
+
# 10s delay gives Claude time to generate + post the reply before we die
|
|
932
|
+
threading.Timer(10.0, lambda: os.kill(os.getpid(), _sig.SIGTERM)).start()
|
|
932
933
|
restart_method = "SIGTERM → systemd restart"
|
|
933
934
|
|
|
934
935
|
steps.append({"step": "restart", "status": "scheduled", "method": restart_method})
|
|
@@ -1032,6 +1033,8 @@ async def _handle_with_cli(
|
|
|
1032
1033
|
reply = _ACTION_RE.sub("", output).strip()
|
|
1033
1034
|
is_done = "[DONE]" in reply
|
|
1034
1035
|
reply = reply.replace("[DONE]", "").strip()
|
|
1036
|
+
if not reply:
|
|
1037
|
+
reply = "Done."
|
|
1035
1038
|
|
|
1036
1039
|
history.append({"role": "user", "content": message})
|
|
1037
1040
|
history.append({"role": "assistant", "content": reply})
|
|
@@ -1114,6 +1117,8 @@ async def handle_message(
|
|
|
1114
1117
|
reply = " ".join(text_parts).strip()
|
|
1115
1118
|
is_done = "[DONE]" in reply
|
|
1116
1119
|
reply = reply.replace("[DONE]", "").strip()
|
|
1120
|
+
if not reply:
|
|
1121
|
+
reply = "Done."
|
|
1117
1122
|
history.append({"role": "assistant", "content": response.content})
|
|
1118
1123
|
return reply, is_done
|
|
1119
1124
|
|
|
@@ -293,6 +293,12 @@ async def _dispatch(event: dict, client, cfg_loader, store) -> None:
|
|
|
293
293
|
if not text:
|
|
294
294
|
return
|
|
295
295
|
|
|
296
|
+
# Allowlist check — if SLACK_ALLOWED_USERS is configured, silently ignore everyone else
|
|
297
|
+
allowed = cfg_loader.sentinel.slack_allowed_users
|
|
298
|
+
if allowed and user_id not in allowed:
|
|
299
|
+
logger.warning("Boss: ignoring message from unauthorised user %s", user_id)
|
|
300
|
+
return
|
|
301
|
+
|
|
296
302
|
user_name = await _resolve_name(client, user_id)
|
|
297
303
|
|
|
298
304
|
status, pos, session = await _queue.try_activate(user_id, user_name, channel)
|
|
@@ -318,6 +324,8 @@ async def _run_turn(session: _Session, message: str, client, cfg_loader, store)
|
|
|
318
324
|
# Typing indicator
|
|
319
325
|
await _post(client, channel, "_thinking..._")
|
|
320
326
|
|
|
327
|
+
reply = ""
|
|
328
|
+
is_done = True
|
|
321
329
|
try:
|
|
322
330
|
reply, is_done = await handle_message(
|
|
323
331
|
message, session.history, cfg_loader, store,
|
|
@@ -325,8 +333,7 @@ async def _run_turn(session: _Session, message: str, client, cfg_loader, store)
|
|
|
325
333
|
)
|
|
326
334
|
except Exception as e:
|
|
327
335
|
logger.exception("Sentinel Boss error: %s", e)
|
|
328
|
-
|
|
329
|
-
is_done = True
|
|
336
|
+
reply = f":warning: Unhandled error: {e}"
|
|
330
337
|
|
|
331
338
|
await _post(client, channel, reply)
|
|
332
339
|
|
|
@@ -347,6 +354,8 @@ async def _run_turn(session: _Session, message: str, client, cfg_loader, store)
|
|
|
347
354
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
348
355
|
|
|
349
356
|
async def _post(client, channel: str, text: str) -> None:
|
|
357
|
+
if not text:
|
|
358
|
+
return
|
|
350
359
|
try:
|
|
351
360
|
await client.chat_postMessage(channel=channel, text=text)
|
|
352
361
|
except Exception as e:
|
|
@@ -36,6 +36,12 @@ WORKSPACE_DIR=./workspace
|
|
|
36
36
|
# Note: requires conversations:read scope on the Slack App if using channel name
|
|
37
37
|
# SLACK_CHANNEL=devops-sentinel
|
|
38
38
|
|
|
39
|
+
# Allowlist of Slack user IDs permitted to give Sentinel Boss commands (RECOMMENDED).
|
|
40
|
+
# If set, all other users are silently ignored — even in the configured channel.
|
|
41
|
+
# Find a user ID in Slack: click their profile → ⋯ More → Copy member ID (starts with U).
|
|
42
|
+
# Comma-separated. Leave unset to allow anyone who can reach the bot (less secure).
|
|
43
|
+
# SLACK_ALLOWED_USERS=U01AB2CD3EF, U09GH8IJ7KL
|
|
44
|
+
|
|
39
45
|
# Passive bot watcher — seed the watch list on startup with known bot IDs.
|
|
40
46
|
# Sentinel will passively queue every message from these bots as issues (no @mention needed).
|
|
41
47
|
# You can also add bots at runtime: "@Sentinel listen to @alertbot for project 1881"
|