mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: remove legacy alert rules, superseded by automations
Alert rules already auto-migrate into automations on boot and the old engine stands down after. Deletes the rule/firing tables, routes, UI, and AI tools; keeps notification channels since automations still use them. Also fixes automations' notify step, which still called the deleted repository.
This commit is contained in:
1 parent
e0f14f2ecf
commit
e372b0eda7
101 files changed
+29118
-5539
No files matched your search
@@ -13,7 +13,7 @@ export function buildSystemPrompt(options: {
|
||||
}): string {
|
||||
const lines: string[] = [
|
||||
"You are the assistant built into Termix, a self-hosted server management app.",
|
||||
"You help the user manage their servers, snippets, automations, fleets and alerts.",
|
||||
"You help the user manage their servers, snippets, automations and fleets.",
|
||||
"",
|
||||
"How you work:",
|
||||
"- You start with no knowledge of this user's setup. Call a read tool to find out anything you need.",
|
||||
@@ -24,7 +24,7 @@ export function buildSystemPrompt(options: {
|
||||
"- Do exactly what was asked, and nothing beyond it. A question is a request for an answer, not for changes.",
|
||||
"- Questions like 'what is running on this server', 'check this host' or 'why is this slow' are answered with information. Read, then report. Do not propose anything.",
|
||||
"- Only propose a change when the user asked for one, in words like 'add', 'create', 'set up', 'fix' or 'change'.",
|
||||
"- Do not propose follow-up work you thought of yourself: no monitoring, no alert rules, no scripts, no snippets, no cleanup, unless that is what was requested.",
|
||||
"- Do not propose follow-up work you thought of yourself: no monitoring automations, no scripts, no snippets, no cleanup, unless that is what was requested.",
|
||||
"- If you think something is worth doing, say so in one sentence and stop. Let the user ask.",
|
||||
"- One request means one proposal at most. Do not bundle extras alongside it.",
|
||||
"",
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
* This plugin runs on the main thread rather than in a worker for the same
|
||||
* reason ssh-terminal, docker and host-metrics do (see
|
||||
* src/backend/plugins/first-party.ts): it writes directly across hosts,
|
||||
* snippets, fleets, alert rules and automations through their repositories,
|
||||
* snippets, fleets and automations through their repositories,
|
||||
* runs approved commands over the shared SSH pool via resolveHostById and
|
||||
* withConnection, and streams its chat replies as server-sent events. None
|
||||
* of that fits the worker ctx -- ctx.hosts is a 13-field read-only view with
|
||||
* no write methods and no snippets/fleets/alerts/automations surface at all,
|
||||
* no write methods and no snippets/fleets/automations surface at all,
|
||||
* and ctx.http.route replies with a single postMessage value rather than a
|
||||
* stream.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
createCurrentAlertRepository,
|
||||
createCurrentAutomationRepository,
|
||||
createCurrentFleetRepository,
|
||||
createCurrentHostRepository,
|
||||
@@ -217,35 +216,6 @@ export async function applyProposal(
|
||||
};
|
||||
}
|
||||
|
||||
case "propose_create_alert_rule": {
|
||||
const created = await createCurrentAlertRepository().createAlertRule({
|
||||
userId,
|
||||
name: requireString(payload.name, "name"),
|
||||
hostId:
|
||||
payload.hostId === null || payload.hostId === undefined
|
||||
? null
|
||||
: requireNumber(payload.hostId, "hostId"),
|
||||
enabled: true,
|
||||
triggerType: requireString(payload.triggerType, "triggerType"),
|
||||
thresholdValue:
|
||||
payload.thresholdValue === null ||
|
||||
payload.thresholdValue === undefined
|
||||
? null
|
||||
: Number(payload.thresholdValue),
|
||||
thresholdDurationSeconds:
|
||||
payload.thresholdDurationSeconds === null ||
|
||||
payload.thresholdDurationSeconds === undefined
|
||||
? null
|
||||
: Number(payload.thresholdDurationSeconds),
|
||||
cooldownMinutes: Number(payload.cooldownMinutes) || 15,
|
||||
channelIds: [],
|
||||
} as any);
|
||||
return {
|
||||
ok: true,
|
||||
summary: `Created alert rule ${(created as any)?.name ?? ""}`.trim(),
|
||||
};
|
||||
}
|
||||
|
||||
case "propose_create_automation": {
|
||||
// Reuses the same validator the automations route runs, so an
|
||||
// LLM-authored definition is held to exactly the human standard.
|
||||
|
||||
@@ -216,40 +216,6 @@ export const proposeTools: AiTool[] = [
|
||||
hostIds: args.hostIds ?? [],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "propose_create_alert_rule",
|
||||
description:
|
||||
"Propose a new alert rule that fires when a host metric crosses a threshold.",
|
||||
category: "propose",
|
||||
parameters: objectSchema(
|
||||
{
|
||||
name: str("Rule name"),
|
||||
hostId: num("Host id to watch, or omit to watch all hosts"),
|
||||
triggerType: str(
|
||||
"What to watch, for example cpu, memory, disk or host_status",
|
||||
),
|
||||
thresholdValue: num("Threshold to compare against"),
|
||||
thresholdDurationSeconds: num(
|
||||
"How long the breach must persist before firing",
|
||||
),
|
||||
cooldownMinutes: num("Minimum minutes between repeat firings"),
|
||||
},
|
||||
["name", "triggerType"],
|
||||
),
|
||||
handler: async (args) =>
|
||||
proposal(
|
||||
"propose_create_alert_rule",
|
||||
`Create alert rule ${String(args.name)}`,
|
||||
{
|
||||
name: args.name,
|
||||
hostId: args.hostId ?? null,
|
||||
triggerType: args.triggerType,
|
||||
thresholdValue: args.thresholdValue ?? null,
|
||||
thresholdDurationSeconds: args.thresholdDurationSeconds ?? null,
|
||||
cooldownMinutes: args.cooldownMinutes ?? 15,
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "propose_run_command",
|
||||
description:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
createCurrentAlertRepository,
|
||||
createCurrentNotificationChannelRepository,
|
||||
createCurrentAutomationRepository,
|
||||
createCurrentCommandHistoryRepository,
|
||||
createCurrentFleetRepository,
|
||||
@@ -190,30 +190,6 @@ export const readTools: AiTool[] = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_alert_rules",
|
||||
description:
|
||||
"List the user's alert rules with their thresholds and enabled state.",
|
||||
category: "read",
|
||||
parameters: objectSchema({}),
|
||||
handler: async (_args, context) => {
|
||||
const rules = await createCurrentAlertRepository().listAlertRules(
|
||||
context.userId,
|
||||
);
|
||||
return {
|
||||
rules: rules.map((rule) => ({
|
||||
id: rule.id,
|
||||
name: rule.name,
|
||||
hostId: rule.host_id,
|
||||
enabled: rule.enabled === 1,
|
||||
triggerType: rule.trigger_type,
|
||||
thresholdValue: rule.threshold_value,
|
||||
thresholdDurationSeconds: rule.threshold_duration_seconds,
|
||||
cooldownMinutes: rule.cooldown_minutes,
|
||||
})),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_notification_channels",
|
||||
description:
|
||||
@@ -222,7 +198,7 @@ export const readTools: AiTool[] = [
|
||||
parameters: objectSchema({}),
|
||||
handler: async (_args, context) => {
|
||||
const channels =
|
||||
await createCurrentAlertRepository().listNotificationChannels(
|
||||
await createCurrentNotificationChannelRepository().listNotificationChannels(
|
||||
context.userId,
|
||||
);
|
||||
return {
|
||||
@@ -235,35 +211,6 @@ export const readTools: AiTool[] = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get_alert_firings",
|
||||
description:
|
||||
"Recent alert firings, newest first. Use this to answer questions about what has been alerting.",
|
||||
category: "read",
|
||||
parameters: objectSchema({
|
||||
limit: num("How many firings to return (default 25, max 100)"),
|
||||
}),
|
||||
handler: async (args, context) => {
|
||||
const limit = Math.min(Math.max(Number(args.limit) || 25, 1), 100);
|
||||
const result = await createCurrentAlertRepository().listAlertFirings({
|
||||
userId: context.userId,
|
||||
limit,
|
||||
offset: 0,
|
||||
});
|
||||
return {
|
||||
firings: (result.firings ?? []).map((firing) => ({
|
||||
id: firing.id,
|
||||
ruleName: firing.rule_name,
|
||||
hostName: firing.host_name,
|
||||
firedAt: firing.fired_at,
|
||||
resolvedAt: firing.resolved_at,
|
||||
severity: firing.severity,
|
||||
message: firing.message,
|
||||
acknowledged: firing.acknowledged === 1,
|
||||
})),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_homepage_items",
|
||||
description: "List the user's homepage service-link tiles.",
|
||||
|
||||
Reference in new issue
Block a user