@misterhuydo/sentinel 1.2.8 → 1.2.9
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/session.json +2 -2
- package/package.json +1 -1
- package/python/sentinel/fix_engine.py +1 -0
- package/python/sentinel/main.py +39 -29
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-23T12:00:29.548Z",
|
|
3
|
+
"checkpoint_at": "2026-03-23T12:00:29.550Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/package.json
CHANGED
package/python/sentinel/main.py
CHANGED
|
@@ -90,27 +90,31 @@ async def _handle_error(event: ErrorEvent, cfg_loader: ConfigLoader, store: Stat
|
|
|
90
90
|
if status != "patch" or patch_path is None:
|
|
91
91
|
outcome = "skipped" if status in ("skip", "needs_human") else "failed"
|
|
92
92
|
store.record_fix(event.fingerprint, outcome, repo_name=repo.repo_name)
|
|
93
|
-
|
|
93
|
+
# For log-detected errors: NEEDS_HUMAN -> DM/channel; SKIP -> email only (not spam)
|
|
94
94
|
if status == "needs_human":
|
|
95
|
-
# marker holds the reason string for needs_human
|
|
96
95
|
notify_fix_blocked(sentinel, event.source, event.message,
|
|
97
96
|
reason=marker, repo_name=repo.repo_name,
|
|
98
|
-
submitter_user_id=
|
|
97
|
+
submitter_user_id="")
|
|
99
98
|
else:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
send_failure_notification(sentinel, {
|
|
100
|
+
"source": event.source,
|
|
101
|
+
"message": event.message,
|
|
102
|
+
"repo_name": repo.repo_name,
|
|
103
|
+
"reason": f"Claude Code returned {status.upper()}",
|
|
104
|
+
"body": event.full_text()[:500],
|
|
105
|
+
})
|
|
104
106
|
return
|
|
105
107
|
|
|
106
108
|
commit_status, commit_hash = apply_and_commit(event, patch_path, repo, sentinel)
|
|
107
109
|
if commit_status != "committed":
|
|
108
110
|
store.record_fix(event.fingerprint, "failed", repo_name=repo.repo_name)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
send_failure_notification(sentinel, {
|
|
112
|
+
"source": event.source,
|
|
113
|
+
"message": event.message,
|
|
114
|
+
"repo_name": repo.repo_name,
|
|
115
|
+
"reason": "Patch was generated but commit/tests failed",
|
|
116
|
+
"body": event.full_text()[:500],
|
|
117
|
+
})
|
|
114
118
|
return
|
|
115
119
|
|
|
116
120
|
branch, pr_url = publish(event, repo, sentinel, commit_hash)
|
|
@@ -184,16 +188,12 @@ async def _handle_issue(event: IssueEvent, cfg_loader: ConfigLoader, store: Stat
|
|
|
184
188
|
if status != "patch" or patch_path is None:
|
|
185
189
|
store.record_fix(event.fingerprint, "skipped" if status in ("skip", "needs_human") else "failed",
|
|
186
190
|
repo_name=repo.repo_name)
|
|
191
|
+
# For user-submitted issues: always notify (person is waiting)
|
|
187
192
|
submitter_uid = getattr(event, "submitter_user_id", "")
|
|
188
|
-
if status == "needs_human"
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
else:
|
|
193
|
-
notify_fix_blocked(sentinel, event.source, event.message,
|
|
194
|
-
reason=f"Claude Code returned {status.upper()}",
|
|
195
|
-
repo_name=repo.repo_name,
|
|
196
|
-
submitter_user_id=submitter_uid)
|
|
193
|
+
reason_text = marker if status == "needs_human" else f"Claude Code returned {status.upper()}"
|
|
194
|
+
notify_fix_blocked(sentinel, event.source, event.message,
|
|
195
|
+
reason=reason_text, repo_name=repo.repo_name,
|
|
196
|
+
submitter_user_id=submitter_uid)
|
|
197
197
|
mark_done(event.issue_file)
|
|
198
198
|
return
|
|
199
199
|
|
|
@@ -307,21 +307,31 @@ async def poll_cycle(cfg_loader: ConfigLoader, store: StateStore):
|
|
|
307
307
|
|
|
308
308
|
# -- Health URL checks -------------------------------------------------------
|
|
309
309
|
if cfg_loader.repos:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
import asyncio as _asyncio
|
|
311
|
+
_loop = _asyncio.get_event_loop()
|
|
312
|
+
health_results = await _loop.run_in_executor(
|
|
313
|
+
None,
|
|
314
|
+
lambda: evaluate_repos(
|
|
315
|
+
cfg_loader.repos, cfg_loader.log_sources,
|
|
316
|
+
cfg_loader.sentinel.workspace_dir, store=store,
|
|
317
|
+
)
|
|
313
318
|
)
|
|
314
319
|
for hr in health_results:
|
|
315
320
|
if hr["action"] == "fix":
|
|
316
321
|
fp = f"health-{hr['repo_name']}"
|
|
317
322
|
store.record_error(fp, f"health_checker/{hr['repo_name']}", hr["message"])
|
|
318
323
|
if not store.fix_attempted_recently(fp, hours=6):
|
|
319
|
-
|
|
324
|
+
from .log_parser import ErrorEvent as _EE
|
|
325
|
+
from datetime import datetime, timezone as _tz
|
|
326
|
+
synth = _EE(
|
|
320
327
|
source=f"health_checker/{hr['repo_name']}",
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
328
|
+
log_file="",
|
|
329
|
+
timestamp=datetime.now(_tz.utc).isoformat(),
|
|
330
|
+
level="ERROR",
|
|
331
|
+
thread="health_checker",
|
|
332
|
+
logger_name="health_checker",
|
|
333
|
+
message=f"App startup failure detected: {hr['message']}",
|
|
334
|
+
stack_trace=[hr["startup_failure_line"]] if hr["startup_failure_line"] else [],
|
|
325
335
|
)
|
|
326
336
|
synth.fingerprint = fp
|
|
327
337
|
await _handle_error(synth, cfg_loader, store)
|