@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.
File without changes
package/.cairn/.hint-lock CHANGED
@@ -1 +1 @@
1
- 2026-03-22T12:03:28.852Z
1
+ 2026-03-22T14:06:29.625Z
@@ -1,6 +1,6 @@
1
1
  {
2
- "message": "Auto-checkpoint at 2026-03-22T11:59:57.164Z",
3
- "checkpoint_at": "2026-03-22T11:59:57.165Z",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.0.54",
3
+ "version": "1.0.56",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -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(2)
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
- threading.Timer(2.0, lambda: os.kill(os.getpid(), _sig.SIGTERM)).start()
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
- await _post(client, channel, f":warning: Unhandled error: {e}")
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"