@misterhuydo/sentinel 1.0.86 → 1.0.88
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/.hint-lock
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-23T06:03:48.001Z
|
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-23T06:00:14.460Z",
|
|
3
|
+
"checkpoint_at": "2026-03-23T06:00:14.461Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/package.json
CHANGED
|
@@ -113,6 +113,10 @@ What you can do (tools available):
|
|
|
113
113
|
21. tail_log — Fetch the last N lines of a log source live, without a grep filter.
|
|
114
114
|
e.g. "show recent SSOLWA logs", "tail STS", "last 200 lines from 1881 logs"
|
|
115
115
|
|
|
116
|
+
22. post_file — Upload a text file to the Slack conversation (diff, log excerpt, report, CSV).
|
|
117
|
+
Use when output is too large for chat, or the user asks to download/export something.
|
|
118
|
+
e.g. "give me that as a file", "export the log", "send me the diff"
|
|
119
|
+
|
|
116
120
|
When someone asks what you can do, what you support, what your capabilities are, or how you can help,
|
|
117
121
|
reply with a short summary grouped by category:
|
|
118
122
|
|
|
@@ -154,6 +158,9 @@ reply with a short summary grouped by category:
|
|
|
154
158
|
*Self-management*
|
|
155
159
|
• `upgrade_sentinel` — git pull + pip install + restart — "upgrade sentinel", "update yourself"
|
|
156
160
|
|
|
161
|
+
*File sharing*
|
|
162
|
+
• `post_file` — upload a file to Slack — "give me that as a file", "export the log", "send me the diff"
|
|
163
|
+
|
|
157
164
|
Tone: direct, professional, like a senior engineer who owns the system.
|
|
158
165
|
Don't pad responses. Don't say "Great question!" or "Certainly!".
|
|
159
166
|
If you don't know something, use a tool to find out before saying you don't know.
|
|
@@ -551,6 +558,34 @@ _TOOLS = [
|
|
|
551
558
|
"required": ["source"],
|
|
552
559
|
},
|
|
553
560
|
},
|
|
561
|
+
{
|
|
562
|
+
"name": "post_file",
|
|
563
|
+
"description": (
|
|
564
|
+
"Upload a text file directly to the Slack conversation so the user can read or download it. "
|
|
565
|
+
"Use when: output is too large for a chat message, the user asks to 'download', 'export', or "
|
|
566
|
+
"'send as a file', or when formatted content (diffs, logs, CSVs, reports) is clearer as a file. "
|
|
567
|
+
"e.g. 'give me that as a file', 'export the log', 'send me the diff for PR #41', "
|
|
568
|
+
"'download the health report', 'export recent errors as CSV'"
|
|
569
|
+
),
|
|
570
|
+
"input_schema": {
|
|
571
|
+
"type": "object",
|
|
572
|
+
"properties": {
|
|
573
|
+
"content": {
|
|
574
|
+
"type": "string",
|
|
575
|
+
"description": "The full text content of the file to upload",
|
|
576
|
+
},
|
|
577
|
+
"filename": {
|
|
578
|
+
"type": "string",
|
|
579
|
+
"description": "Filename with extension, e.g. 'fix-ab12.diff', 'sentinel-report.txt', 'errors.csv', 'ssolwa.log'",
|
|
580
|
+
},
|
|
581
|
+
"title": {
|
|
582
|
+
"type": "string",
|
|
583
|
+
"description": "Optional display title shown above the file in Slack (defaults to filename)",
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
"required": ["content", "filename"],
|
|
587
|
+
},
|
|
588
|
+
},
|
|
554
589
|
]
|
|
555
590
|
|
|
556
591
|
|
|
@@ -617,7 +652,7 @@ def _git_pull(path: Path) -> dict:
|
|
|
617
652
|
|
|
618
653
|
# ── Tool execution ────────────────────────────────────────────────────────────
|
|
619
654
|
|
|
620
|
-
async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=None, user_id: str = "") -> str:
|
|
655
|
+
async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=None, user_id: str = "", channel: str = "") -> str:
|
|
621
656
|
if name == "get_status":
|
|
622
657
|
hours = int(inputs.get("hours", 24))
|
|
623
658
|
errors = store.get_recent_errors(hours)
|
|
@@ -1206,6 +1241,27 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
1206
1241
|
results.append({"source": props.stem, "error": str(e)})
|
|
1207
1242
|
return json.dumps({"results": results})
|
|
1208
1243
|
|
|
1244
|
+
if name == "post_file":
|
|
1245
|
+
if not slack_client or not channel:
|
|
1246
|
+
return json.dumps({"error": "No Slack channel context — cannot upload file"})
|
|
1247
|
+
content = inputs.get("content", "")
|
|
1248
|
+
filename = inputs.get("filename", "sentinel-output.txt")
|
|
1249
|
+
title = inputs.get("title", filename)
|
|
1250
|
+
if not content:
|
|
1251
|
+
return json.dumps({"error": "No content provided"})
|
|
1252
|
+
try:
|
|
1253
|
+
await slack_client.files_upload_v2(
|
|
1254
|
+
channel=channel,
|
|
1255
|
+
content=content,
|
|
1256
|
+
filename=filename,
|
|
1257
|
+
title=title,
|
|
1258
|
+
)
|
|
1259
|
+
logger.info("Boss post_file: uploaded %s (%d bytes) to %s", filename, len(content), channel)
|
|
1260
|
+
return json.dumps({"ok": True, "filename": filename, "bytes": len(content)})
|
|
1261
|
+
except Exception as e:
|
|
1262
|
+
logger.warning("Boss post_file failed: %s", e)
|
|
1263
|
+
return json.dumps({"error": str(e)})
|
|
1264
|
+
|
|
1209
1265
|
if name == "my_stats":
|
|
1210
1266
|
hours = int(inputs.get("hours", 168))
|
|
1211
1267
|
errors = store.get_recent_errors(hours)
|
|
@@ -1379,9 +1435,12 @@ async def _handle_with_cli(
|
|
|
1379
1435
|
)
|
|
1380
1436
|
history_text += f"\n{role}: {content}"
|
|
1381
1437
|
|
|
1438
|
+
slack_mention = f"<@{user_id}>" if user_id else (user_name or "")
|
|
1382
1439
|
prompt = (
|
|
1383
1440
|
_SYSTEM
|
|
1384
|
-
+ (f"\nYou are speaking with: {user_name}" if user_name else "")
|
|
1441
|
+
+ (f"\nYou are speaking with: {user_name} (Slack mention: {slack_mention})" if user_name else "")
|
|
1442
|
+
+ "\nAlways start your reply by addressing the user directly using their Slack mention, e.g. \"<@U123> here is what I found...\"."
|
|
1443
|
+
+ " Never use their plain name — always use the <@USER_ID> format so Slack highlights it."
|
|
1385
1444
|
+ f"\n\nCurrent time: {ts}"
|
|
1386
1445
|
+ f"\nSentinel status: {'⏸ PAUSED' if paused else '▶ RUNNING'}"
|
|
1387
1446
|
+ f"\nManaged repos: {', '.join(repos) if repos else '(none configured)'}"
|
|
@@ -1464,6 +1523,7 @@ async def _handle_with_api(
|
|
|
1464
1523
|
user_name: str = "",
|
|
1465
1524
|
user_id: str = "",
|
|
1466
1525
|
attachments: list | None = None,
|
|
1526
|
+
channel: str = "",
|
|
1467
1527
|
) -> tuple[str, bool]:
|
|
1468
1528
|
import anthropic
|
|
1469
1529
|
|
|
@@ -1475,9 +1535,12 @@ async def _handle_with_api(
|
|
|
1475
1535
|
ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
|
1476
1536
|
known_projects = [_read_project_name(d) for d in _find_project_dirs()]
|
|
1477
1537
|
log_sources = list(cfg_loader.log_sources.keys())
|
|
1538
|
+
slack_mention = f"<@{user_id}>" if user_id else (user_name or "")
|
|
1478
1539
|
system = (
|
|
1479
1540
|
_SYSTEM
|
|
1480
|
-
+ (f"\nYou are speaking with: {user_name}" if user_name else "")
|
|
1541
|
+
+ (f"\nYou are speaking with: {user_name} (Slack mention: {slack_mention})" if user_name else "")
|
|
1542
|
+
+ "\nAlways start your reply by addressing the user directly using their Slack mention, e.g. \"<@U123> here is what I found...\"."
|
|
1543
|
+
+ " Never use their plain name — always use the <@USER_ID> format so Slack highlights it."
|
|
1481
1544
|
+ f"\n\nCurrent time: {ts}"
|
|
1482
1545
|
+ f"\nSentinel status: {'⏸ PAUSED' if paused else '▶ RUNNING'}"
|
|
1483
1546
|
+ f"\nManaged repos: {', '.join(repos) if repos else '(none configured)'}"
|
|
@@ -1524,7 +1587,7 @@ async def _handle_with_api(
|
|
|
1524
1587
|
messages.append({"role": "assistant", "content": response.content})
|
|
1525
1588
|
tool_results = []
|
|
1526
1589
|
for tc in tool_blocks:
|
|
1527
|
-
result = await _run_tool(tc.name, tc.input, cfg_loader, store, slack_client=slack_client, user_id=user_id)
|
|
1590
|
+
result = await _run_tool(tc.name, tc.input, cfg_loader, store, slack_client=slack_client, user_id=user_id, channel=channel)
|
|
1528
1591
|
logger.info("Boss tool: %s(%s) → %s", tc.name, tc.input, result[:120])
|
|
1529
1592
|
tool_results.append({
|
|
1530
1593
|
"type": "tool_result",
|
|
@@ -1545,6 +1608,7 @@ async def handle_message(
|
|
|
1545
1608
|
user_name: str = "",
|
|
1546
1609
|
user_id: str = "",
|
|
1547
1610
|
attachments: list | None = None,
|
|
1611
|
+
channel: str = "",
|
|
1548
1612
|
) -> tuple[str, bool]:
|
|
1549
1613
|
"""
|
|
1550
1614
|
Process one user message through the Sentinel Boss (Claude with tool use).
|
|
@@ -1566,7 +1630,7 @@ async def handle_message(
|
|
|
1566
1630
|
import anthropic # noqa: F401
|
|
1567
1631
|
return await _handle_with_api(
|
|
1568
1632
|
message, history, cfg_loader, store, slack_client=slack_client,
|
|
1569
|
-
user_name=user_name, user_id=user_id, attachments=attachments,
|
|
1633
|
+
user_name=user_name, user_id=user_id, attachments=attachments, channel=channel,
|
|
1570
1634
|
)
|
|
1571
1635
|
except Exception as api_err:
|
|
1572
1636
|
err_str = str(api_err)
|
|
@@ -389,6 +389,7 @@ async def _run_turn(session: _Session, message: str, client, cfg_loader, store,
|
|
|
389
389
|
user_name=session.user_name,
|
|
390
390
|
user_id=session.user_id,
|
|
391
391
|
attachments=attachments or [],
|
|
392
|
+
channel=channel,
|
|
392
393
|
)
|
|
393
394
|
except Exception as e:
|
|
394
395
|
logger.exception("Sentinel Boss error: %s", e)
|