@misterhuydo/sentinel 1.0.85 → 1.0.87

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-23T05:01:48.297Z
1
+ 2026-03-23T05:33:10.957Z
@@ -1,6 +1,6 @@
1
1
  {
2
- "message": "Auto-checkpoint at 2026-03-23T05:25:59.517Z",
3
- "checkpoint_at": "2026-03-23T05:25:59.518Z",
2
+ "message": "Auto-checkpoint at 2026-03-23T05:55:20.486Z",
3
+ "checkpoint_at": "2026-03-23T05:55:20.487Z",
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.85",
3
+ "version": "1.0.87",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -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)
@@ -1464,6 +1520,7 @@ async def _handle_with_api(
1464
1520
  user_name: str = "",
1465
1521
  user_id: str = "",
1466
1522
  attachments: list | None = None,
1523
+ channel: str = "",
1467
1524
  ) -> tuple[str, bool]:
1468
1525
  import anthropic
1469
1526
 
@@ -1524,7 +1581,7 @@ async def _handle_with_api(
1524
1581
  messages.append({"role": "assistant", "content": response.content})
1525
1582
  tool_results = []
1526
1583
  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)
1584
+ result = await _run_tool(tc.name, tc.input, cfg_loader, store, slack_client=slack_client, user_id=user_id, channel=channel)
1528
1585
  logger.info("Boss tool: %s(%s) → %s", tc.name, tc.input, result[:120])
1529
1586
  tool_results.append({
1530
1587
  "type": "tool_result",
@@ -1545,6 +1602,7 @@ async def handle_message(
1545
1602
  user_name: str = "",
1546
1603
  user_id: str = "",
1547
1604
  attachments: list | None = None,
1605
+ channel: str = "",
1548
1606
  ) -> tuple[str, bool]:
1549
1607
  """
1550
1608
  Process one user message through the Sentinel Boss (Claude with tool use).
@@ -1566,7 +1624,7 @@ async def handle_message(
1566
1624
  import anthropic # noqa: F401
1567
1625
  return await _handle_with_api(
1568
1626
  message, history, cfg_loader, store, slack_client=slack_client,
1569
- user_name=user_name, user_id=user_id, attachments=attachments,
1627
+ user_name=user_name, user_id=user_id, attachments=attachments, channel=channel,
1570
1628
  )
1571
1629
  except Exception as api_err:
1572
1630
  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)
@@ -391,7 +391,7 @@ class StateStore:
391
391
  ).fetchall()
392
392
  return [dict(r) for r in rows]
393
393
 
394
- def save_conversation(self, user_id: str, history: list):
394
+ def save_conversation(self, user_id: str, history: list):
395
395
  """Persist the last N messages of a user conversation to SQLite."""
396
396
  trimmed = history[-self._MAX_HISTORY_MESSAGES:]
397
397
  with self._conn() as conn: