@adonis-agora/telescope 0.1.0
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/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/configure.d.ts +16 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +75 -0
- package/dist/configure.js.map +1 -0
- package/dist/providers/telescope_ai_provider.d.ts +20 -0
- package/dist/providers/telescope_ai_provider.d.ts.map +1 -0
- package/dist/providers/telescope_ai_provider.js +45 -0
- package/dist/providers/telescope_ai_provider.js.map +1 -0
- package/dist/providers/telescope_alerts_provider.d.ts +23 -0
- package/dist/providers/telescope_alerts_provider.d.ts.map +1 -0
- package/dist/providers/telescope_alerts_provider.js +72 -0
- package/dist/providers/telescope_alerts_provider.js.map +1 -0
- package/dist/providers/telescope_provider.d.ts +43 -0
- package/dist/providers/telescope_provider.d.ts.map +1 -0
- package/dist/providers/telescope_provider.js +103 -0
- package/dist/providers/telescope_provider.js.map +1 -0
- package/dist/providers/telescope_ui_provider.d.ts +21 -0
- package/dist/providers/telescope_ui_provider.d.ts.map +1 -0
- package/dist/providers/telescope_ui_provider.js +119 -0
- package/dist/providers/telescope_ui_provider.js.map +1 -0
- package/dist/providers/telescope_watchers_provider.d.ts +31 -0
- package/dist/providers/telescope_watchers_provider.d.ts.map +1 -0
- package/dist/providers/telescope_watchers_provider.js +116 -0
- package/dist/providers/telescope_watchers_provider.js.map +1 -0
- package/dist/src/ai/define_config.d.ts +56 -0
- package/dist/src/ai/define_config.d.ts.map +1 -0
- package/dist/src/ai/define_config.js +39 -0
- package/dist/src/ai/define_config.js.map +1 -0
- package/dist/src/ai/diagnoser.d.ts +34 -0
- package/dist/src/ai/diagnoser.d.ts.map +1 -0
- package/dist/src/ai/diagnoser.js +74 -0
- package/dist/src/ai/diagnoser.js.map +1 -0
- package/dist/src/ai/diagnosis_cache.d.ts +43 -0
- package/dist/src/ai/diagnosis_cache.d.ts.map +1 -0
- package/dist/src/ai/diagnosis_cache.js +56 -0
- package/dist/src/ai/diagnosis_cache.js.map +1 -0
- package/dist/src/ai/factory.d.ts +15 -0
- package/dist/src/ai/factory.d.ts.map +1 -0
- package/dist/src/ai/factory.js +24 -0
- package/dist/src/ai/factory.js.map +1 -0
- package/dist/src/ai/index.d.ts +14 -0
- package/dist/src/ai/index.d.ts.map +1 -0
- package/dist/src/ai/index.js +15 -0
- package/dist/src/ai/index.js.map +1 -0
- package/dist/src/ai/prompt.d.ts +31 -0
- package/dist/src/ai/prompt.d.ts.map +1 -0
- package/dist/src/ai/prompt.js +66 -0
- package/dist/src/ai/prompt.js.map +1 -0
- package/dist/src/ai/telescope_ai_diagnoser.d.ts +79 -0
- package/dist/src/ai/telescope_ai_diagnoser.d.ts.map +1 -0
- package/dist/src/ai/telescope_ai_diagnoser.js +111 -0
- package/dist/src/ai/telescope_ai_diagnoser.js.map +1 -0
- package/dist/src/alerts/alert_channel.d.ts +69 -0
- package/dist/src/alerts/alert_channel.d.ts.map +1 -0
- package/dist/src/alerts/alert_channel.js +114 -0
- package/dist/src/alerts/alert_channel.js.map +1 -0
- package/dist/src/alerts/alert_rule.d.ts +86 -0
- package/dist/src/alerts/alert_rule.d.ts.map +1 -0
- package/dist/src/alerts/alert_rule.js +2 -0
- package/dist/src/alerts/alert_rule.js.map +1 -0
- package/dist/src/alerts/alerter.d.ts +72 -0
- package/dist/src/alerts/alerter.d.ts.map +1 -0
- package/dist/src/alerts/alerter.js +248 -0
- package/dist/src/alerts/alerter.js.map +1 -0
- package/dist/src/alerts/define_config.d.ts +68 -0
- package/dist/src/alerts/define_config.d.ts.map +1 -0
- package/dist/src/alerts/define_config.js +57 -0
- package/dist/src/alerts/define_config.js.map +1 -0
- package/dist/src/alerts/exception_source.d.ts +44 -0
- package/dist/src/alerts/exception_source.d.ts.map +1 -0
- package/dist/src/alerts/exception_source.js +79 -0
- package/dist/src/alerts/exception_source.js.map +1 -0
- package/dist/src/alerts/index.d.ts +16 -0
- package/dist/src/alerts/index.d.ts.map +1 -0
- package/dist/src/alerts/index.js +17 -0
- package/dist/src/alerts/index.js.map +1 -0
- package/dist/src/alerts/new_exception_tracker.d.ts +50 -0
- package/dist/src/alerts/new_exception_tracker.d.ts.map +1 -0
- package/dist/src/alerts/new_exception_tracker.js +74 -0
- package/dist/src/alerts/new_exception_tracker.js.map +1 -0
- package/dist/src/alerts/parse_duration.d.ts +10 -0
- package/dist/src/alerts/parse_duration.d.ts.map +1 -0
- package/dist/src/alerts/parse_duration.js +27 -0
- package/dist/src/alerts/parse_duration.js.map +1 -0
- package/dist/src/alerts/slack_format.d.ts +60 -0
- package/dist/src/alerts/slack_format.d.ts.map +1 -0
- package/dist/src/alerts/slack_format.js +122 -0
- package/dist/src/alerts/slack_format.js.map +1 -0
- package/dist/src/context_accessor.d.ts +30 -0
- package/dist/src/context_accessor.d.ts.map +1 -0
- package/dist/src/context_accessor.js +20 -0
- package/dist/src/context_accessor.js.map +1 -0
- package/dist/src/define_config.d.ts +109 -0
- package/dist/src/define_config.d.ts.map +1 -0
- package/dist/src/define_config.js +38 -0
- package/dist/src/define_config.js.map +1 -0
- package/dist/src/diagnostics_registry.d.ts +46 -0
- package/dist/src/diagnostics_registry.d.ts.map +1 -0
- package/dist/src/diagnostics_registry.js +34 -0
- package/dist/src/diagnostics_registry.js.map +1 -0
- package/dist/src/diagnostics_watcher.d.ts +72 -0
- package/dist/src/diagnostics_watcher.d.ts.map +1 -0
- package/dist/src/diagnostics_watcher.js +119 -0
- package/dist/src/diagnostics_watcher.js.map +1 -0
- package/dist/src/entry.d.ts +81 -0
- package/dist/src/entry.d.ts.map +1 -0
- package/dist/src/entry.js +34 -0
- package/dist/src/entry.js.map +1 -0
- package/dist/src/exception_family_hash.d.ts +29 -0
- package/dist/src/exception_family_hash.d.ts.map +1 -0
- package/dist/src/exception_family_hash.js +30 -0
- package/dist/src/exception_family_hash.js.map +1 -0
- package/dist/src/exception_watcher.d.ts +66 -0
- package/dist/src/exception_watcher.d.ts.map +1 -0
- package/dist/src/exception_watcher.js +94 -0
- package/dist/src/exception_watcher.js.map +1 -0
- package/dist/src/extension/registry.d.ts +17 -0
- package/dist/src/extension/registry.d.ts.map +1 -0
- package/dist/src/extension/registry.js +56 -0
- package/dist/src/extension/registry.js.map +1 -0
- package/dist/src/extension/types.d.ts +158 -0
- package/dist/src/extension/types.d.ts.map +1 -0
- package/dist/src/extension/types.js +5 -0
- package/dist/src/extension/types.js.map +1 -0
- package/dist/src/index.d.ts +36 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +28 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/redaction/redact.d.ts +93 -0
- package/dist/src/redaction/redact.d.ts.map +1 -0
- package/dist/src/redaction/redact.js +184 -0
- package/dist/src/redaction/redact.js.map +1 -0
- package/dist/src/redaction/redacting_store.d.ts +28 -0
- package/dist/src/redaction/redacting_store.d.ts.map +1 -0
- package/dist/src/redaction/redacting_store.js +49 -0
- package/dist/src/redaction/redacting_store.js.map +1 -0
- package/dist/src/registry.d.ts +26 -0
- package/dist/src/registry.d.ts.map +1 -0
- package/dist/src/registry.js +28 -0
- package/dist/src/registry.js.map +1 -0
- package/dist/src/request_watcher.d.ts +44 -0
- package/dist/src/request_watcher.d.ts.map +1 -0
- package/dist/src/request_watcher.js +37 -0
- package/dist/src/request_watcher.js.map +1 -0
- package/dist/src/service.d.ts +36 -0
- package/dist/src/service.d.ts.map +1 -0
- package/dist/src/service.js +65 -0
- package/dist/src/service.js.map +1 -0
- package/dist/src/store.d.ts +56 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +2 -0
- package/dist/src/store.js.map +1 -0
- package/dist/src/stores/factory.d.ts +61 -0
- package/dist/src/stores/factory.d.ts.map +1 -0
- package/dist/src/stores/factory.js +42 -0
- package/dist/src/stores/factory.js.map +1 -0
- package/dist/src/stores/lucid.d.ts +138 -0
- package/dist/src/stores/lucid.d.ts.map +1 -0
- package/dist/src/stores/lucid.js +257 -0
- package/dist/src/stores/lucid.js.map +1 -0
- package/dist/src/stores/memory.d.ts +31 -0
- package/dist/src/stores/memory.d.ts.map +1 -0
- package/dist/src/stores/memory.js +117 -0
- package/dist/src/stores/memory.js.map +1 -0
- package/dist/src/telescope_middleware.d.ts +19 -0
- package/dist/src/telescope_middleware.d.ts.map +1 -0
- package/dist/src/telescope_middleware.js +56 -0
- package/dist/src/telescope_middleware.js.map +1 -0
- package/dist/src/ui/api.d.ts +49 -0
- package/dist/src/ui/api.d.ts.map +1 -0
- package/dist/src/ui/api.js +155 -0
- package/dist/src/ui/api.js.map +1 -0
- package/dist/src/ui/dashboard.d.ts +8 -0
- package/dist/src/ui/dashboard.d.ts.map +1 -0
- package/dist/src/ui/dashboard.html +626 -0
- package/dist/src/ui/dashboard.js +29 -0
- package/dist/src/ui/dashboard.js.map +1 -0
- package/dist/src/ui/define_config.d.ts +87 -0
- package/dist/src/ui/define_config.d.ts.map +1 -0
- package/dist/src/ui/define_config.js +104 -0
- package/dist/src/ui/define_config.js.map +1 -0
- package/dist/src/ui/extension_api.d.ts +23 -0
- package/dist/src/ui/extension_api.d.ts.map +1 -0
- package/dist/src/ui/extension_api.js +50 -0
- package/dist/src/ui/extension_api.js.map +1 -0
- package/dist/src/ui/guard.d.ts +33 -0
- package/dist/src/ui/guard.d.ts.map +1 -0
- package/dist/src/ui/guard.js +47 -0
- package/dist/src/ui/guard.js.map +1 -0
- package/dist/src/ui/http.d.ts +47 -0
- package/dist/src/ui/http.d.ts.map +1 -0
- package/dist/src/ui/http.js +43 -0
- package/dist/src/ui/http.js.map +1 -0
- package/dist/src/ui/index.d.ts +12 -0
- package/dist/src/ui/index.d.ts.map +1 -0
- package/dist/src/ui/index.js +13 -0
- package/dist/src/ui/index.js.map +1 -0
- package/dist/src/watchers/cache_watcher.d.ts +60 -0
- package/dist/src/watchers/cache_watcher.d.ts.map +1 -0
- package/dist/src/watchers/cache_watcher.js +72 -0
- package/dist/src/watchers/cache_watcher.js.map +1 -0
- package/dist/src/watchers/define_config.d.ts +38 -0
- package/dist/src/watchers/define_config.d.ts.map +1 -0
- package/dist/src/watchers/define_config.js +17 -0
- package/dist/src/watchers/define_config.js.map +1 -0
- package/dist/src/watchers/emitter.d.ts +32 -0
- package/dist/src/watchers/emitter.d.ts.map +1 -0
- package/dist/src/watchers/emitter.js +2 -0
- package/dist/src/watchers/emitter.js.map +1 -0
- package/dist/src/watchers/http_client_watcher.d.ts +74 -0
- package/dist/src/watchers/http_client_watcher.d.ts.map +1 -0
- package/dist/src/watchers/http_client_watcher.js +168 -0
- package/dist/src/watchers/http_client_watcher.js.map +1 -0
- package/dist/src/watchers/index.d.ts +19 -0
- package/dist/src/watchers/index.d.ts.map +1 -0
- package/dist/src/watchers/index.js +19 -0
- package/dist/src/watchers/index.js.map +1 -0
- package/dist/src/watchers/logs_watcher.d.ts +82 -0
- package/dist/src/watchers/logs_watcher.d.ts.map +1 -0
- package/dist/src/watchers/logs_watcher.js +145 -0
- package/dist/src/watchers/logs_watcher.js.map +1 -0
- package/dist/src/watchers/lucid_query_watcher.d.ts +64 -0
- package/dist/src/watchers/lucid_query_watcher.d.ts.map +1 -0
- package/dist/src/watchers/lucid_query_watcher.js +84 -0
- package/dist/src/watchers/lucid_query_watcher.js.map +1 -0
- package/dist/src/watchers/mail_watcher.d.ts +51 -0
- package/dist/src/watchers/mail_watcher.d.ts.map +1 -0
- package/dist/src/watchers/mail_watcher.js +93 -0
- package/dist/src/watchers/mail_watcher.js.map +1 -0
- package/dist/src/watchers/normalize_http_target.d.ts +17 -0
- package/dist/src/watchers/normalize_http_target.d.ts.map +1 -0
- package/dist/src/watchers/normalize_http_target.js +41 -0
- package/dist/src/watchers/normalize_http_target.js.map +1 -0
- package/dist/src/watchers/query_family_hash.d.ts +8 -0
- package/dist/src/watchers/query_family_hash.d.ts.map +1 -0
- package/dist/src/watchers/query_family_hash.js +31 -0
- package/dist/src/watchers/query_family_hash.js.map +1 -0
- package/dist/src/watchers/record.d.ts +22 -0
- package/dist/src/watchers/record.d.ts.map +1 -0
- package/dist/src/watchers/record.js +48 -0
- package/dist/src/watchers/record.js.map +1 -0
- package/dist/stubs/config/telescope.stub +56 -0
- package/dist/stubs/config/telescope_ai.stub +36 -0
- package/dist/stubs/config/telescope_alerts.stub +47 -0
- package/dist/stubs/config/telescope_ui.stub +40 -0
- package/dist/stubs/config/telescope_watchers.stub +30 -0
- package/dist/stubs/database/migrations/create_telescope_entries_table.stub +39 -0
- package/dist/stubs/main.d.ts +6 -0
- package/dist/stubs/main.d.ts.map +1 -0
- package/dist/stubs/main.js +7 -0
- package/dist/stubs/main.js.map +1 -0
- package/package.json +140 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cap on stack frames sent to the model. The TOP frames carry the throw site and
|
|
3
|
+
* the immediate callers — the highest-signal part for root-causing — while a deep
|
|
4
|
+
* tail mostly burns input tokens. Clipping keeps the prompt bounded and cheap.
|
|
5
|
+
*/
|
|
6
|
+
const STACK_FRAME_LIMIT = 25;
|
|
7
|
+
/**
|
|
8
|
+
* The system prompt. Frames the model as a senior engineer triaging a PRODUCTION
|
|
9
|
+
* exception and pins the OUTPUT CONTRACT to a strict JSON object so the result is
|
|
10
|
+
* machine-parseable (cause / fix / confidence) for the dashboard and any
|
|
11
|
+
* downstream alerting. We ask for confidence explicitly so an operator can
|
|
12
|
+
* calibrate trust — a low-confidence guess is still useful but should be read as
|
|
13
|
+
* such.
|
|
14
|
+
*/
|
|
15
|
+
export const SYSTEM_PROMPT = [
|
|
16
|
+
'You are a senior backend engineer triaging a production exception for a teammate',
|
|
17
|
+
'in an AdonisJS application. You are given an exception (name, message, stack) and,',
|
|
18
|
+
'when available, the HTTP route it came from and other telescope entries recorded',
|
|
19
|
+
'in the same request trace.',
|
|
20
|
+
'',
|
|
21
|
+
'Respond with ONLY a single JSON object — no prose, no markdown fences — with',
|
|
22
|
+
'exactly these keys:',
|
|
23
|
+
' "cause": string — two to three sentences on WHAT failed and WHY, grounded in',
|
|
24
|
+
' the message and stack. Do not restate the exception verbatim.',
|
|
25
|
+
' "fix": string — a concrete, actionable smallest-correct change. Mention a',
|
|
26
|
+
' guard/validation if the input looks malformed.',
|
|
27
|
+
' "confidence": one of "high", "medium", "low" — your confidence in the diagnosis.',
|
|
28
|
+
'',
|
|
29
|
+
'Do not invent file paths or framework details unsupported by the stack. If the',
|
|
30
|
+
'stack is missing, reason from the message and route and lower your confidence.',
|
|
31
|
+
].join('\n');
|
|
32
|
+
/**
|
|
33
|
+
* Assemble the USER message from an exception entry and any related trace entries.
|
|
34
|
+
* Plain labelled sections (not JSON) read better for an LLM. Absent fields are
|
|
35
|
+
* omitted rather than rendered as `null`, so the model isn't nudged to comment on
|
|
36
|
+
* missing data.
|
|
37
|
+
*/
|
|
38
|
+
export function buildUserPrompt(entry, related = []) {
|
|
39
|
+
const content = entry.content;
|
|
40
|
+
const lines = [];
|
|
41
|
+
lines.push(`Exception: ${content.name}: ${content.message}`);
|
|
42
|
+
const routeLine = [content.method, content.url].filter((part) => part).join(' ');
|
|
43
|
+
if (routeLine !== '')
|
|
44
|
+
lines.push(`Route: ${routeLine}`);
|
|
45
|
+
if (entry.traceId !== null)
|
|
46
|
+
lines.push(`Trace: ${entry.traceId}`);
|
|
47
|
+
if (typeof content.stack === 'string' && content.stack.trim() !== '') {
|
|
48
|
+
lines.push('', 'Stack:', clipStack(content.stack));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
lines.push('', 'Stack: (none captured)');
|
|
52
|
+
}
|
|
53
|
+
if (related.length > 0) {
|
|
54
|
+
lines.push('', 'Other entries in the same trace:');
|
|
55
|
+
for (const r of related) {
|
|
56
|
+
lines.push(`- [${r.type}] ${r.summary}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return lines.join('\n');
|
|
60
|
+
}
|
|
61
|
+
/** Keep at most the first {@link STACK_FRAME_LIMIT} lines of the stack. */
|
|
62
|
+
function clipStack(stack) {
|
|
63
|
+
return stack.split('\n').slice(0, STACK_FRAME_LIMIT).join('\n');
|
|
64
|
+
}
|
|
65
|
+
export { STACK_FRAME_LIMIT };
|
|
66
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../../src/ai/prompt.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,kFAAkF;IAClF,oFAAoF;IACpF,kFAAkF;IAClF,4BAA4B;IAC5B,EAAE;IACF,8EAA8E;IAC9E,qBAAqB;IACrB,gFAAgF;IAChF,0EAA0E;IAC1E,6EAA6E;IAC7E,yDAAyD;IACzD,oFAAoF;IACpF,EAAE;IACF,gFAAgF;IAChF,gFAAgF;CACjF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAQb;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAmC,EACnC,UAAiC,EAAE;IAEnC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,cAAc,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE7D,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjF,IAAI,SAAS,KAAK,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,SAAS,EAAE,CAAC,CAAC;IACxD,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAElE,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,wBAAwB,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,kCAAkC,CAAC,CAAC;QACnD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,2EAA2E;AAC3E,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClE,CAAC;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Entry } from '../entry.js';
|
|
2
|
+
import { type Diagnosis, type ExceptionEntryContent } from './diagnoser.js';
|
|
3
|
+
import { type DiagnosisStore } from './diagnosis_cache.js';
|
|
4
|
+
/**
|
|
5
|
+
* The minimal slice of the Anthropic SDK client the diagnoser uses. Declared
|
|
6
|
+
* structurally so tests can inject a fake without the real SDK, and so the
|
|
7
|
+
* package depends on `@anthropic-ai/sdk` only as a PEER (the host owns the
|
|
8
|
+
* version). The real `Anthropic` instance satisfies this shape.
|
|
9
|
+
*/
|
|
10
|
+
export interface AnthropicMessagesClient {
|
|
11
|
+
messages: {
|
|
12
|
+
create(body: {
|
|
13
|
+
model: string;
|
|
14
|
+
max_tokens: number;
|
|
15
|
+
system?: string;
|
|
16
|
+
messages: {
|
|
17
|
+
role: 'user' | 'assistant';
|
|
18
|
+
content: string;
|
|
19
|
+
}[];
|
|
20
|
+
}): Promise<{
|
|
21
|
+
content: Array<{
|
|
22
|
+
type: string;
|
|
23
|
+
text?: string;
|
|
24
|
+
}>;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Options for {@link TelescopeAiDiagnoser}. */
|
|
29
|
+
export interface TelescopeAiDiagnoserOptions {
|
|
30
|
+
/** The Anthropic SDK client (or any value matching {@link AnthropicMessagesClient}). */
|
|
31
|
+
client: AnthropicMessagesClient;
|
|
32
|
+
/** Claude model id (e.g. `claude-sonnet-4-6`). */
|
|
33
|
+
model: string;
|
|
34
|
+
/** Hard cap on generated tokens per diagnosis. */
|
|
35
|
+
maxTokens: number;
|
|
36
|
+
/** Whether diagnosis is active. When `false`, {@link diagnose} is a no-op. Default `true`. */
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
/** Pluggable cache. Defaults to an in-memory {@link DiagnosisCache}. */
|
|
39
|
+
cache?: DiagnosisStore;
|
|
40
|
+
/** Failure log sink. Defaults to `console.warn`. */
|
|
41
|
+
logger?: (message: string) => void;
|
|
42
|
+
}
|
|
43
|
+
/** Per-call options for {@link TelescopeAiDiagnoser.diagnose}. */
|
|
44
|
+
export interface DiagnoseOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Other telescope entries recorded in the same trace (queries, the request,
|
|
47
|
+
* diagnostics). Summarized into the prompt as extra context. Optional.
|
|
48
|
+
*/
|
|
49
|
+
related?: Entry[];
|
|
50
|
+
/** Bypass the cache and force a fresh API call (still writes the result back). */
|
|
51
|
+
force?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* AI-assisted diagnosis of `exception` telescope entries via the Anthropic Claude
|
|
55
|
+
* Messages API. {@link diagnose} builds a prompt from the exception entry (plus
|
|
56
|
+
* any related trace entries), calls Claude, parses a structured
|
|
57
|
+
* {@link Diagnosis}, and caches it by the exception's family hash so the same
|
|
58
|
+
* error is never diagnosed twice.
|
|
59
|
+
*
|
|
60
|
+
* Safe by construction: disabled / no client → no-op (`null`); a missing family
|
|
61
|
+
* hash skips caching; a model or parse failure is logged and resolves to `null`
|
|
62
|
+
* rather than throwing into the caller's path.
|
|
63
|
+
*/
|
|
64
|
+
export declare class TelescopeAiDiagnoser {
|
|
65
|
+
private readonly client;
|
|
66
|
+
private readonly model;
|
|
67
|
+
private readonly maxTokens;
|
|
68
|
+
private readonly enabled;
|
|
69
|
+
private readonly cache;
|
|
70
|
+
private readonly logger;
|
|
71
|
+
constructor(options: TelescopeAiDiagnoserOptions);
|
|
72
|
+
/**
|
|
73
|
+
* Diagnose one exception entry. Returns the cached diagnosis when the entry's
|
|
74
|
+
* family has already been diagnosed (no second API call), otherwise calls Claude
|
|
75
|
+
* once and caches the result. Resolves to `null` when disabled or on any failure.
|
|
76
|
+
*/
|
|
77
|
+
diagnose(entry: Entry<ExceptionEntryContent>, options?: DiagnoseOptions): Promise<Diagnosis | null>;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=telescope_ai_diagnoser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telescope_ai_diagnoser.d.ts","sourceRoot":"","sources":["../../../src/ai/telescope_ai_diagnoser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,qBAAqB,EAAkB,MAAM,gBAAgB,CAAC;AAC5F,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3E;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE;QACR,MAAM,CAAC,IAAI,EAAE;YACX,KAAK,EAAE,MAAM,CAAC;YACd,UAAU,EAAE,MAAM,CAAC;YACnB,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,QAAQ,EAAE;gBAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;gBAAC,OAAO,EAAE,MAAM,CAAA;aAAE,EAAE,CAAC;SAC7D,GAAG,OAAO,CAAC;YAAE,OAAO,EAAE,KAAK,CAAC;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC,CAAA;SAAE,CAAC,CAAC;KAClE,CAAC;CACH;AAED,gDAAgD;AAChD,MAAM,WAAW,2BAA2B;IAC1C,wFAAwF;IACxF,MAAM,EAAE,uBAAuB,CAAC;IAChC,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,oDAAoD;IACpD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;IAClB,kFAAkF;IAClF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;gBAEvC,OAAO,EAAE,2BAA2B;IAShD;;;;OAIG;IACG,QAAQ,CACZ,KAAK,EAAE,KAAK,CAAC,qBAAqB,CAAC,EACnC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;CAgD7B"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { parseDiagnosis } from './diagnoser.js';
|
|
2
|
+
import { DiagnosisCache } from './diagnosis_cache.js';
|
|
3
|
+
import { SYSTEM_PROMPT, buildUserPrompt } from './prompt.js';
|
|
4
|
+
/**
|
|
5
|
+
* AI-assisted diagnosis of `exception` telescope entries via the Anthropic Claude
|
|
6
|
+
* Messages API. {@link diagnose} builds a prompt from the exception entry (plus
|
|
7
|
+
* any related trace entries), calls Claude, parses a structured
|
|
8
|
+
* {@link Diagnosis}, and caches it by the exception's family hash so the same
|
|
9
|
+
* error is never diagnosed twice.
|
|
10
|
+
*
|
|
11
|
+
* Safe by construction: disabled / no client → no-op (`null`); a missing family
|
|
12
|
+
* hash skips caching; a model or parse failure is logged and resolves to `null`
|
|
13
|
+
* rather than throwing into the caller's path.
|
|
14
|
+
*/
|
|
15
|
+
export class TelescopeAiDiagnoser {
|
|
16
|
+
client;
|
|
17
|
+
model;
|
|
18
|
+
maxTokens;
|
|
19
|
+
enabled;
|
|
20
|
+
cache;
|
|
21
|
+
logger;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.client = options.client;
|
|
24
|
+
this.model = options.model;
|
|
25
|
+
this.maxTokens = options.maxTokens;
|
|
26
|
+
this.enabled = options.enabled ?? true;
|
|
27
|
+
this.cache = options.cache ?? new DiagnosisCache();
|
|
28
|
+
this.logger = options.logger ?? ((message) => console.warn(message));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Diagnose one exception entry. Returns the cached diagnosis when the entry's
|
|
32
|
+
* family has already been diagnosed (no second API call), otherwise calls Claude
|
|
33
|
+
* once and caches the result. Resolves to `null` when disabled or on any failure.
|
|
34
|
+
*/
|
|
35
|
+
async diagnose(entry, options = {}) {
|
|
36
|
+
if (!this.enabled)
|
|
37
|
+
return null;
|
|
38
|
+
const familyHash = entry.familyHash;
|
|
39
|
+
if (familyHash !== null && options.force !== true) {
|
|
40
|
+
const cached = this.cache.get(familyHash);
|
|
41
|
+
if (cached !== null)
|
|
42
|
+
return { ...cached, cached: true };
|
|
43
|
+
}
|
|
44
|
+
const userPrompt = buildUserPrompt(entry, summarizeRelated(options.related ?? []));
|
|
45
|
+
let text;
|
|
46
|
+
try {
|
|
47
|
+
const response = await this.client.messages.create({
|
|
48
|
+
model: this.model,
|
|
49
|
+
max_tokens: this.maxTokens,
|
|
50
|
+
system: SYSTEM_PROMPT,
|
|
51
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
52
|
+
});
|
|
53
|
+
text = extractText(response);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
this.logger(`Telescope AI: diagnosis API call failed: ${asMessage(error)}`);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = parseDiagnosis(text);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
this.logger(`Telescope AI: could not parse diagnosis: ${asMessage(error)}`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const diagnosis = {
|
|
68
|
+
cause: parsed.cause,
|
|
69
|
+
fix: parsed.fix,
|
|
70
|
+
confidence: parsed.confidence,
|
|
71
|
+
model: this.model,
|
|
72
|
+
cached: false,
|
|
73
|
+
};
|
|
74
|
+
if (familyHash !== null) {
|
|
75
|
+
// Store the un-flagged form; reads stamp `cached: true` on the way out.
|
|
76
|
+
this.cache.set(familyHash, diagnosis);
|
|
77
|
+
}
|
|
78
|
+
return diagnosis;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Concatenate the text blocks of a Messages API response. */
|
|
82
|
+
function extractText(response) {
|
|
83
|
+
return response.content
|
|
84
|
+
.filter((block) => block.type === 'text' && typeof block.text === 'string')
|
|
85
|
+
.map((block) => block.text ?? '')
|
|
86
|
+
.join('\n')
|
|
87
|
+
.trim();
|
|
88
|
+
}
|
|
89
|
+
/** Build a short, bounded summary of each related entry for the prompt. */
|
|
90
|
+
function summarizeRelated(related) {
|
|
91
|
+
const summaries = [];
|
|
92
|
+
for (const entry of related) {
|
|
93
|
+
if (entry.type === 'exception')
|
|
94
|
+
continue; // the subject itself / sibling exceptions
|
|
95
|
+
let summary;
|
|
96
|
+
try {
|
|
97
|
+
summary = JSON.stringify(entry.content);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
summary = '(uninspectable content)';
|
|
101
|
+
}
|
|
102
|
+
if (summary.length > 500)
|
|
103
|
+
summary = `${summary.slice(0, 500)}…`;
|
|
104
|
+
summaries.push({ type: entry.type, summary });
|
|
105
|
+
}
|
|
106
|
+
return summaries;
|
|
107
|
+
}
|
|
108
|
+
function asMessage(error) {
|
|
109
|
+
return error instanceof Error ? error.message : String(error);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=telescope_ai_diagnoser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telescope_ai_diagnoser.js","sourceRoot":"","sources":["../../../src/ai/telescope_ai_diagnoser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { AlertPayload } from './alert_rule.js';
|
|
2
|
+
import { type SlackChannelOptions } from './slack_format.js';
|
|
3
|
+
/**
|
|
4
|
+
* A delivery destination for a fired alert. An alert fans out to EVERY configured
|
|
5
|
+
* channel concurrently, and a single channel failing NEVER blocks the others (the
|
|
6
|
+
* {@link Alerter} isolates and warn-logs failures per channel). Implementations
|
|
7
|
+
* may reject freely — the alerter catches it; the contract is simply "deliver this
|
|
8
|
+
* payload, or reject, and we'll log it".
|
|
9
|
+
*
|
|
10
|
+
* `name` is a stable, human-readable identifier used only for rate-limited failure
|
|
11
|
+
* logging (e.g. `"slack"`, `"webhook"`, `"console"`) so an operator can tell WHICH
|
|
12
|
+
* destination is failing without leaking its URL.
|
|
13
|
+
*/
|
|
14
|
+
export interface AlertChannel {
|
|
15
|
+
/** Stable identifier for failure logging (never the raw URL/secret). */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Deliver the payload. Rejecting is fine — the alerter isolates + logs it. */
|
|
18
|
+
send(alert: AlertPayload): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Injectable `fetch` seam so tests can drive the HTTP channels deterministically
|
|
22
|
+
* without a real network. Mirrors the subset of `fetch` the channels use.
|
|
23
|
+
*/
|
|
24
|
+
export type ChannelFetch = (url: string, init: {
|
|
25
|
+
method: string;
|
|
26
|
+
headers: Record<string, string>;
|
|
27
|
+
body: string;
|
|
28
|
+
signal: AbortSignal;
|
|
29
|
+
}) => Promise<unknown>;
|
|
30
|
+
/**
|
|
31
|
+
* Generic JSON webhook channel — POSTs the {@link AlertPayload} as-is so any relay
|
|
32
|
+
* that parses the raw body works.
|
|
33
|
+
*
|
|
34
|
+
* @param url Destination that receives `POST <AlertPayload as JSON>`.
|
|
35
|
+
* @param fetchImpl Test seam; defaults to global `fetch`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function webhookChannel(url: string, fetchImpl?: ChannelFetch): AlertChannel;
|
|
38
|
+
/**
|
|
39
|
+
* Slack-formatted channel — POSTs Block Kit JSON to a Slack incoming webhook so
|
|
40
|
+
* the message renders as a rich card (severity header, fielded context, a
|
|
41
|
+
* truncated stack snippet, and a deep link to the dashboard entry when
|
|
42
|
+
* `dashboardUrl` is configured) instead of a raw JSON blob.
|
|
43
|
+
*
|
|
44
|
+
* @param url Slack incoming webhook URL.
|
|
45
|
+
* @param options Formatting knobs (`username`/`iconEmoji` overrides).
|
|
46
|
+
* @param fetchImpl Test seam; defaults to global `fetch`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function slackChannel(url: string, options?: SlackChannelOptions, fetchImpl?: ChannelFetch): AlertChannel;
|
|
49
|
+
/** The sink a {@link consoleChannel} writes a one-line summary to. */
|
|
50
|
+
export type ConsoleSink = (message: string) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Console channel — logs a one-line summary of each alert. The zero-config default
|
|
53
|
+
* destination: useful in development and as a safety net so an alert is never
|
|
54
|
+
* silently dropped when no remote channel is configured.
|
|
55
|
+
*
|
|
56
|
+
* @param sink Where to write; defaults to `console.warn`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function consoleChannel(sink?: ConsoleSink): AlertChannel;
|
|
59
|
+
/**
|
|
60
|
+
* Custom channel — the escape hatch. The host supplies an arbitrary async sink
|
|
61
|
+
* (send an email, publish to SNS, page someone, …) and Telescope calls it with the
|
|
62
|
+
* rich {@link AlertPayload}. As with every channel, a rejection is isolated and
|
|
63
|
+
* logged by the alerter, so the host's `fn` may freely throw.
|
|
64
|
+
*
|
|
65
|
+
* @param fn Async sink invoked with the fired alert payload.
|
|
66
|
+
* @param name Identifier for failure logging. Defaults to `"custom"`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function customChannel(fn: (alert: AlertPayload) => Promise<void>, name?: string): AlertChannel;
|
|
69
|
+
//# sourceMappingURL=alert_channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert_channel.d.ts","sourceRoot":"","sources":["../../../src/alerts/alert_channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,KAAK,mBAAmB,EAAsB,MAAM,mBAAmB,CAAC;AAEjF;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAQD;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,KACzF,OAAO,CAAC,OAAO,CAAC,CAAC;AAqCtB;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,GAAG,YAAY,CAQlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,mBAAmB,EAC7B,SAAS,CAAC,EAAE,YAAY,GACvB,YAAY,CAQd;AAED,sEAAsE;AACtE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAapD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,YAAY,CAS/D;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,EAC1C,IAAI,SAAW,GACd,YAAY,CAEd"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { formatSlackMessage } from './slack_format.js';
|
|
2
|
+
/**
|
|
3
|
+
* POST timeout for the built-in HTTP channels. A hung endpoint must never stall
|
|
4
|
+
* the alerter's fan-out, so every request is raced against an abort timer.
|
|
5
|
+
*/
|
|
6
|
+
const HTTP_CHANNEL_TIMEOUT_MS = 5_000;
|
|
7
|
+
/**
|
|
8
|
+
* POST a JSON body to `url` with a hard abort timeout. Shared by the webhook and
|
|
9
|
+
* Slack channels — the ONLY difference between them is the body shape, so the
|
|
10
|
+
* transport lives here once. Rejects on network error / timeout / non-2xx; the
|
|
11
|
+
* alerter turns that rejection into a rate-limited warn.
|
|
12
|
+
*/
|
|
13
|
+
async function postJson(fetchImpl, url, body) {
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const timeout = setTimeout(() => controller.abort(), HTTP_CHANNEL_TIMEOUT_MS);
|
|
16
|
+
timeout.unref?.();
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetchImpl(url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'content-type': 'application/json' },
|
|
21
|
+
body: JSON.stringify(body),
|
|
22
|
+
signal: controller.signal,
|
|
23
|
+
});
|
|
24
|
+
// A real `fetch` resolves even on a 4xx/5xx; surface those as failures so a
|
|
25
|
+
// misconfigured webhook is warn-logged rather than silently swallowed. Test
|
|
26
|
+
// doubles that resolve `undefined` are treated as success.
|
|
27
|
+
if (response !== undefined &&
|
|
28
|
+
response !== null &&
|
|
29
|
+
typeof response === 'object' &&
|
|
30
|
+
'ok' in response &&
|
|
31
|
+
response.ok === false) {
|
|
32
|
+
const status = 'status' in response ? response.status : 'unknown';
|
|
33
|
+
throw new Error(`HTTP ${String(status)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
clearTimeout(timeout);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generic JSON webhook channel — POSTs the {@link AlertPayload} as-is so any relay
|
|
42
|
+
* that parses the raw body works.
|
|
43
|
+
*
|
|
44
|
+
* @param url Destination that receives `POST <AlertPayload as JSON>`.
|
|
45
|
+
* @param fetchImpl Test seam; defaults to global `fetch`.
|
|
46
|
+
*/
|
|
47
|
+
export function webhookChannel(url, fetchImpl) {
|
|
48
|
+
const send = fetchImpl ?? ((u, init) => fetch(u, init));
|
|
49
|
+
return {
|
|
50
|
+
name: 'webhook',
|
|
51
|
+
send(alert) {
|
|
52
|
+
return postJson(send, url, alert);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Slack-formatted channel — POSTs Block Kit JSON to a Slack incoming webhook so
|
|
58
|
+
* the message renders as a rich card (severity header, fielded context, a
|
|
59
|
+
* truncated stack snippet, and a deep link to the dashboard entry when
|
|
60
|
+
* `dashboardUrl` is configured) instead of a raw JSON blob.
|
|
61
|
+
*
|
|
62
|
+
* @param url Slack incoming webhook URL.
|
|
63
|
+
* @param options Formatting knobs (`username`/`iconEmoji` overrides).
|
|
64
|
+
* @param fetchImpl Test seam; defaults to global `fetch`.
|
|
65
|
+
*/
|
|
66
|
+
export function slackChannel(url, options, fetchImpl) {
|
|
67
|
+
const send = fetchImpl ?? ((u, init) => fetch(u, init));
|
|
68
|
+
return {
|
|
69
|
+
name: 'slack',
|
|
70
|
+
send(alert) {
|
|
71
|
+
return postJson(send, url, formatSlackMessage(alert, options));
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** One-line human summary of an alert for the console channel. */
|
|
76
|
+
function summarize(alert) {
|
|
77
|
+
const base = `[telescope-alert] ${alert.rule.type} on ${alert.instanceId}`;
|
|
78
|
+
if (alert.exception !== undefined) {
|
|
79
|
+
const { class: cls, message, route, occurrences } = alert.exception;
|
|
80
|
+
const where = route !== null ? ` (${route})` : '';
|
|
81
|
+
return `${base}: ${cls}: ${message}${where} — ${occurrences}× in window`;
|
|
82
|
+
}
|
|
83
|
+
return `${base}: value ${alert.value} (threshold ${alert.threshold})`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Console channel — logs a one-line summary of each alert. The zero-config default
|
|
87
|
+
* destination: useful in development and as a safety net so an alert is never
|
|
88
|
+
* silently dropped when no remote channel is configured.
|
|
89
|
+
*
|
|
90
|
+
* @param sink Where to write; defaults to `console.warn`.
|
|
91
|
+
*/
|
|
92
|
+
export function consoleChannel(sink) {
|
|
93
|
+
const write = sink ?? ((message) => console.warn(message));
|
|
94
|
+
return {
|
|
95
|
+
name: 'console',
|
|
96
|
+
send(alert) {
|
|
97
|
+
write(summarize(alert));
|
|
98
|
+
return Promise.resolve();
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Custom channel — the escape hatch. The host supplies an arbitrary async sink
|
|
104
|
+
* (send an email, publish to SNS, page someone, …) and Telescope calls it with the
|
|
105
|
+
* rich {@link AlertPayload}. As with every channel, a rejection is isolated and
|
|
106
|
+
* logged by the alerter, so the host's `fn` may freely throw.
|
|
107
|
+
*
|
|
108
|
+
* @param fn Async sink invoked with the fired alert payload.
|
|
109
|
+
* @param name Identifier for failure logging. Defaults to `"custom"`.
|
|
110
|
+
*/
|
|
111
|
+
export function customChannel(fn, name = 'custom') {
|
|
112
|
+
return { name, send: fn };
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=alert_channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert_channel.js","sourceRoot":"","sources":["../../../src/alerts/alert_channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { AlertChannel } from './alert_channel.js';
|
|
2
|
+
/**
|
|
3
|
+
* A single alerting rule. Ported (trimmed) from the aviary telescope core. This
|
|
4
|
+
* Adonis port focuses on the two rules the headless `@adonis-agora/telescope` store can
|
|
5
|
+
* back today:
|
|
6
|
+
*
|
|
7
|
+
* - `new-exception` — fires the FIRST time an exception's `familyHash` is seen
|
|
8
|
+
* within `window` (a genuinely NEW error family), and again
|
|
9
|
+
* if the family re-appears after the window has elapsed (the
|
|
10
|
+
* "re-occurring after being resolved" signal). Deduplication
|
|
11
|
+
* is per-process via {@link NewExceptionTracker}.
|
|
12
|
+
* - `exception-rate` — fires when `>= threshold` exception entries were recorded
|
|
13
|
+
* in the trailing `window`.
|
|
14
|
+
*/
|
|
15
|
+
export type AlertRule = {
|
|
16
|
+
type: 'new-exception';
|
|
17
|
+
window: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'exception-rate';
|
|
20
|
+
window: string;
|
|
21
|
+
threshold: number;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Rich exception context attached to a `new-exception` alert. Pulled from the
|
|
25
|
+
* exception {@link Entry} that fired the rule. Absent on rate-rule alerts.
|
|
26
|
+
*/
|
|
27
|
+
export interface ExceptionAlertContext {
|
|
28
|
+
/** Stable family hash that was first-seen this window. */
|
|
29
|
+
familyHash: string;
|
|
30
|
+
/** Exception class name (e.g. `TypeError`). */
|
|
31
|
+
class: string;
|
|
32
|
+
/** Exception message. */
|
|
33
|
+
message: string;
|
|
34
|
+
/** Truncated stack (first frames), or `null`. */
|
|
35
|
+
stack: string | null;
|
|
36
|
+
/** Request route/uri associated with the exception, or `null`. */
|
|
37
|
+
route: string | null;
|
|
38
|
+
/** Request method, or `null`. */
|
|
39
|
+
method: string | null;
|
|
40
|
+
/** Response status code, or `null`. */
|
|
41
|
+
statusCode: number | null;
|
|
42
|
+
/** Authenticated user id from a `user:<id>` tag, or `null`. */
|
|
43
|
+
user: string | null;
|
|
44
|
+
/** Times this family was seen in the window (>= 1; 1 on first-occurrence). */
|
|
45
|
+
occurrences: number;
|
|
46
|
+
/** Exception entry id (for the dashboard deep link). */
|
|
47
|
+
entryId: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The shape delivered to every {@link AlertChannel} when a rule fires.
|
|
51
|
+
*/
|
|
52
|
+
export interface AlertPayload {
|
|
53
|
+
rule: AlertRule;
|
|
54
|
+
/** The measured value that crossed the threshold. */
|
|
55
|
+
value: number;
|
|
56
|
+
/** The rule's threshold (`threshold`, or `1` for `new-exception`). */
|
|
57
|
+
threshold: number;
|
|
58
|
+
/** ISO-8601 fire time. */
|
|
59
|
+
firedAt: string;
|
|
60
|
+
/** The reporting instance identifier. */
|
|
61
|
+
instanceId: string;
|
|
62
|
+
/** Rich context for `new-exception` alerts; absent for rate rules. */
|
|
63
|
+
exception?: ExceptionAlertContext;
|
|
64
|
+
/** External dashboard URL when configured (lets channels build deep links). */
|
|
65
|
+
dashboardUrl?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Validated, boot-resolved alerting config (durations normalized to ms).
|
|
69
|
+
*/
|
|
70
|
+
export interface ResolvedAlerts {
|
|
71
|
+
/** Whether alerting is active. */
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
/** All delivery destinations. */
|
|
74
|
+
channels: AlertChannel[];
|
|
75
|
+
/** External dashboard URL for deep links, or `null` when unset. */
|
|
76
|
+
dashboardUrl: string | null;
|
|
77
|
+
/** Poll cadence for the exception-entry source, in ms. */
|
|
78
|
+
intervalMs: number;
|
|
79
|
+
/** Per-rule / per-family re-notify suppression, in ms. */
|
|
80
|
+
cooldownMs: number;
|
|
81
|
+
/** The reporting instance identifier. */
|
|
82
|
+
instanceId: string;
|
|
83
|
+
/** Rules to evaluate. */
|
|
84
|
+
rules: AlertRule[];
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=alert_rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert_rule.d.ts","sourceRoot":"","sources":["../../../src/alerts/alert_rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iCAAiC;IACjC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uCAAuC;IACvC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,iCAAiC;IACjC,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,mEAAmE;IACnE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert_rule.js","sourceRoot":"","sources":["../../../src/alerts/alert_rule.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Entry } from '../entry.js';
|
|
2
|
+
import type { ResolvedAlerts } from './alert_rule.js';
|
|
3
|
+
export interface AlerterDeps {
|
|
4
|
+
alerts: ResolvedAlerts;
|
|
5
|
+
/** Wall-clock seam (ms). Defaults to `Date.now`. */
|
|
6
|
+
now?: () => number;
|
|
7
|
+
/** Cap on tracked error families for the `new-exception` rule (test seam). */
|
|
8
|
+
maxFamilies?: number;
|
|
9
|
+
/** Failure log sink. Defaults to `console.warn`. */
|
|
10
|
+
logger?: (message: string) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Wires a source of exception {@link Entry} objects → rule evaluation → channels.
|
|
14
|
+
*
|
|
15
|
+
* Two evaluation paths, both driven by {@link evaluate} (called once per poll with
|
|
16
|
+
* the exception entries observed since the last poll):
|
|
17
|
+
* 1. The `new-exception` rule fires the FIRST time a family hash is seen within
|
|
18
|
+
* its window (and again after the window elapses / a {@link resolveFamily}),
|
|
19
|
+
* deduped by {@link NewExceptionTracker} and rate-limited by the per-family
|
|
20
|
+
* cooldown.
|
|
21
|
+
* 2. The `exception-rate` rule fires when the running count of exception entries
|
|
22
|
+
* in its trailing window crosses the threshold, rate-limited by a per-rule
|
|
23
|
+
* cooldown.
|
|
24
|
+
*
|
|
25
|
+
* Every fired alert fans out to ALL configured channels concurrently; one channel
|
|
26
|
+
* failing never blocks the others. NEVER throws into the caller: a channel failure
|
|
27
|
+
* is warn-logged (rate-limited per channel) and otherwise swallowed.
|
|
28
|
+
*/
|
|
29
|
+
export declare class Alerter {
|
|
30
|
+
private readonly deps;
|
|
31
|
+
private readonly now;
|
|
32
|
+
private readonly logger;
|
|
33
|
+
private readonly tracker;
|
|
34
|
+
/** Per-rule-index last-fired wall time for the rate-rule cooldown. */
|
|
35
|
+
private readonly lastFiredAt;
|
|
36
|
+
/** Per-family last-fired wall time for the `new-exception` cooldown. */
|
|
37
|
+
private readonly lastFiredFamily;
|
|
38
|
+
/** Channels we've already warned about (rate-limit failure logs by name). */
|
|
39
|
+
private readonly warnedChannels;
|
|
40
|
+
/** Sliding-window timestamps of recent exception entries (rate rule). */
|
|
41
|
+
private readonly recentExceptions;
|
|
42
|
+
constructor(deps: AlerterDeps);
|
|
43
|
+
/**
|
|
44
|
+
* Evaluate every rule over a freshly-observed batch of exception entries. Safe
|
|
45
|
+
* to call with an empty batch (rate rules still re-check their window against
|
|
46
|
+
* already-recorded timestamps). Never throws into the caller.
|
|
47
|
+
*/
|
|
48
|
+
evaluate(exceptionEntries: Entry[]): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Explicitly forget a family so its next occurrence pages again immediately —
|
|
51
|
+
* the "resolved → re-occurred" path. Also clears its cooldown.
|
|
52
|
+
*/
|
|
53
|
+
resolveFamily(familyHash: string): void;
|
|
54
|
+
/** Number of error families currently tracked (test/observability seam). */
|
|
55
|
+
get trackedFamilies(): number;
|
|
56
|
+
private evaluateNewException;
|
|
57
|
+
private evaluateExceptionRate;
|
|
58
|
+
/** Find a configured rule of `type` with its array index, or `null`. */
|
|
59
|
+
private findRule;
|
|
60
|
+
private inCooldown;
|
|
61
|
+
private familyInCooldown;
|
|
62
|
+
private buildRatePayload;
|
|
63
|
+
private buildNewExceptionPayload;
|
|
64
|
+
/**
|
|
65
|
+
* Fan the payload out to every channel concurrently. Each channel's failure is
|
|
66
|
+
* isolated (`Promise.allSettled`) and warn-logged ONCE per channel name so a
|
|
67
|
+
* persistently-down destination doesn't flood the logs. Never rejects.
|
|
68
|
+
*/
|
|
69
|
+
private dispatch;
|
|
70
|
+
private warnChannelFailure;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=alerter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alerter.d.ts","sourceRoot":"","sources":["../../../src/alerts/alerter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,KAAK,EAIV,cAAc,EACf,MAAM,iBAAiB,CAAC;AAOzB,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,OAAO;IAaN,OAAO,CAAC,QAAQ,CAAC,IAAI;IAZjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAgB;gBAEpB,IAAI,EAAE,WAAW;IAM9C;;;;OAIG;IACG,QAAQ,CAAC,gBAAgB,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBxD;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAKvC,4EAA4E;IAC5E,IAAI,eAAe,IAAI,MAAM,CAE5B;YAEa,oBAAoB;YAyBpB,qBAAqB;IAiBnC,wEAAwE;IACxE,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,wBAAwB;IAmBhC;;;;OAIG;YACW,QAAQ;IAWtB,OAAO,CAAC,kBAAkB;CAM3B"}
|