openclaw-aegis 1.1.1 → 1.2.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/README.md +4 -1
- package/dist/cli/index.js +285 -86
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +120 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,6 +57,8 @@ Probes: 10 passed, 0 failed
|
|
|
57
57
|
| `aegis check --json` | JSON output for scripting |
|
|
58
58
|
| `aegis status` | Health dashboard with per-probe details |
|
|
59
59
|
| `aegis test-alert` | Send a test notification to all configured channels |
|
|
60
|
+
| `aegis incidents` | Browse past incident logs |
|
|
61
|
+
| `aegis incidents <id>` | Show full timeline for a specific incident |
|
|
60
62
|
|
|
61
63
|
---
|
|
62
64
|
|
|
@@ -70,6 +72,7 @@ Probes: 10 passed, 0 failed
|
|
|
70
72
|
| [Alerts](docs/alerts.md) | Setting up ntfy, Telegram, WhatsApp, Slack, Discord, Email, Pushover, webhooks |
|
|
71
73
|
| [CLI Reference](docs/cli-reference.md) | Every command with examples and options |
|
|
72
74
|
| [Contributing](docs/contributing.md) | Development setup, testing, PR process |
|
|
75
|
+
| [Releasing](docs/releasing.md) | Version bumps, npm publish, GitHub releases |
|
|
73
76
|
|
|
74
77
|
---
|
|
75
78
|
|
|
@@ -111,4 +114,4 @@ OpenClaw Gateway Aegis Sidecar
|
|
|
111
114
|
|
|
112
115
|
MIT — see [LICENSE](LICENSE).
|
|
113
116
|
|
|
114
|
-
Built by [Canary Builds](https://
|
|
117
|
+
Built by [Canary Builds](https://canarybuilds.com).
|
package/dist/cli/index.js
CHANGED
|
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
// src/cli/index.ts
|
|
27
27
|
var import_node_fs = require("fs");
|
|
28
28
|
var import_node_path = require("path");
|
|
29
|
-
var
|
|
29
|
+
var import_commander6 = require("commander");
|
|
30
30
|
|
|
31
31
|
// src/cli/commands/status.ts
|
|
32
32
|
var import_commander = require("commander");
|
|
@@ -254,9 +254,15 @@ var DegradedConfirmation = class {
|
|
|
254
254
|
|
|
255
255
|
// src/health/probes/resolve-pid.ts
|
|
256
256
|
var fs2 = __toESM(require("fs"));
|
|
257
|
+
var os2 = __toESM(require("os"));
|
|
257
258
|
var import_node_child_process = require("child_process");
|
|
258
259
|
function resolvePid(pidSource) {
|
|
259
|
-
|
|
260
|
+
const isPath = pidSource.includes("/");
|
|
261
|
+
if (!isPath) {
|
|
262
|
+
if (os2.platform() === "darwin") {
|
|
263
|
+
const pid2 = resolveFromLaunchd(pidSource);
|
|
264
|
+
if (pid2 !== null) return pid2;
|
|
265
|
+
}
|
|
260
266
|
const pid = resolveFromSystemd(pidSource);
|
|
261
267
|
if (pid !== null) return pid;
|
|
262
268
|
}
|
|
@@ -264,11 +270,11 @@ function resolvePid(pidSource) {
|
|
|
264
270
|
}
|
|
265
271
|
function resolveFromSystemd(unit) {
|
|
266
272
|
try {
|
|
267
|
-
const output = (0, import_node_child_process.execFileSync)(
|
|
268
|
-
"
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
).trim();
|
|
273
|
+
const output = (0, import_node_child_process.execFileSync)("systemctl", ["--user", "show", "-p", "MainPID", "--value", unit], {
|
|
274
|
+
encoding: "utf-8",
|
|
275
|
+
timeout: 3e3,
|
|
276
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
277
|
+
}).trim();
|
|
272
278
|
const pid = parseInt(output, 10);
|
|
273
279
|
if (!isNaN(pid) && pid > 0) return pid;
|
|
274
280
|
return null;
|
|
@@ -276,6 +282,31 @@ function resolveFromSystemd(unit) {
|
|
|
276
282
|
return null;
|
|
277
283
|
}
|
|
278
284
|
}
|
|
285
|
+
function resolveFromLaunchd(label) {
|
|
286
|
+
try {
|
|
287
|
+
const output = (0, import_node_child_process.execFileSync)("launchctl", ["list", label], {
|
|
288
|
+
encoding: "utf-8",
|
|
289
|
+
timeout: 3e3,
|
|
290
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
291
|
+
});
|
|
292
|
+
const pidMatch = output.match(/"PID"\s*=\s*(\d+)/);
|
|
293
|
+
if (pidMatch) {
|
|
294
|
+
const pid = parseInt(pidMatch[1], 10);
|
|
295
|
+
if (!isNaN(pid) && pid > 0) return pid;
|
|
296
|
+
}
|
|
297
|
+
const lines = output.trim().split("\n");
|
|
298
|
+
if (lines.length >= 2) {
|
|
299
|
+
const parts = lines[1].trim().split(/\s+/);
|
|
300
|
+
if (parts[0]) {
|
|
301
|
+
const pid = parseInt(parts[0], 10);
|
|
302
|
+
if (!isNaN(pid) && pid > 0) return pid;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
} catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
279
310
|
function resolveFromFile(pidFile) {
|
|
280
311
|
try {
|
|
281
312
|
if (!fs2.existsSync(pidFile)) return null;
|
|
@@ -462,9 +493,9 @@ async function configProbe(_target, configPath) {
|
|
|
462
493
|
latencyMs: Date.now() - start
|
|
463
494
|
};
|
|
464
495
|
}
|
|
465
|
-
const missingPaths = REQUIRED_CONFIG_PATHS.filter(({ path:
|
|
496
|
+
const missingPaths = REQUIRED_CONFIG_PATHS.filter(({ path: path4 }) => {
|
|
466
497
|
let obj = parsed;
|
|
467
|
-
for (const key of
|
|
498
|
+
for (const key of path4) {
|
|
468
499
|
if (obj === null || typeof obj !== "object" || !(key in obj)) return true;
|
|
469
500
|
obj = obj[key];
|
|
470
501
|
}
|
|
@@ -901,10 +932,7 @@ var HealthMonitor = class extends import_node_events.EventEmitter {
|
|
|
901
932
|
"disk"
|
|
902
933
|
),
|
|
903
934
|
withTimeout(() => logTailProbe(target, this.config.gateway.logPath), "logTail"),
|
|
904
|
-
withTimeout(
|
|
905
|
-
() => websocketProbe(target, this.config.gateway.port, timeout),
|
|
906
|
-
"websocket"
|
|
907
|
-
)
|
|
935
|
+
withTimeout(() => websocketProbe(target, this.config.gateway.port, timeout), "websocket")
|
|
908
936
|
]);
|
|
909
937
|
const probeResults = results.map((r, i) => {
|
|
910
938
|
if (r.status === "fulfilled") return r.value;
|
|
@@ -968,10 +996,12 @@ var statusCommand = new import_commander.Command("status").description("Show cur
|
|
|
968
996
|
};
|
|
969
997
|
const reset = "\x1B[0m";
|
|
970
998
|
const color = bandColors[score.band] ?? "";
|
|
971
|
-
process.stdout.write(
|
|
999
|
+
process.stdout.write(
|
|
1000
|
+
`
|
|
972
1001
|
Health: ${color}${score.band.toUpperCase()}${reset} (score: ${score.total})
|
|
973
1002
|
|
|
974
|
-
`
|
|
1003
|
+
`
|
|
1004
|
+
);
|
|
975
1005
|
for (const probe of score.probeResults) {
|
|
976
1006
|
const icon = probe.healthy ? "\x1B[32m+\x1B[0m" : "\x1B[31m-\x1B[0m";
|
|
977
1007
|
const msg = probe.message ? ` \u2014 ${probe.message}` : "";
|
|
@@ -1000,8 +1030,10 @@ var checkCommand = new import_commander2.Command("check").description("Run all h
|
|
|
1000
1030
|
const failed = score.probeResults.filter((p) => !p.healthy);
|
|
1001
1031
|
process.stdout.write(`Health: ${score.band.toUpperCase()} (score: ${score.total})
|
|
1002
1032
|
`);
|
|
1003
|
-
process.stdout.write(
|
|
1004
|
-
`
|
|
1033
|
+
process.stdout.write(
|
|
1034
|
+
`Probes: ${score.probeResults.length - failed.length} passed, ${failed.length} failed
|
|
1035
|
+
`
|
|
1036
|
+
);
|
|
1005
1037
|
if (failed.length > 0) {
|
|
1006
1038
|
process.stdout.write("\nFailed probes:\n");
|
|
1007
1039
|
for (const probe of failed) {
|
|
@@ -1016,6 +1048,7 @@ var checkCommand = new import_commander2.Command("check").description("Run all h
|
|
|
1016
1048
|
// src/cli/commands/init.ts
|
|
1017
1049
|
var import_commander3 = require("commander");
|
|
1018
1050
|
var fs8 = __toESM(require("fs"));
|
|
1051
|
+
var os3 = __toESM(require("os"));
|
|
1019
1052
|
var readline = __toESM(require("readline"));
|
|
1020
1053
|
function ask(rl, question, defaultValue) {
|
|
1021
1054
|
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
@@ -1030,19 +1063,27 @@ function detectPort() {
|
|
|
1030
1063
|
const configPath = expandHome("~/.openclaw/openclaw.json");
|
|
1031
1064
|
if (fs8.existsSync(configPath)) {
|
|
1032
1065
|
const raw = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
1033
|
-
|
|
1066
|
+
const gateway = raw?.gateway;
|
|
1067
|
+
if (gateway?.port && typeof gateway.port === "number") return gateway.port;
|
|
1034
1068
|
}
|
|
1035
1069
|
} catch {
|
|
1036
1070
|
}
|
|
1037
1071
|
return 3e3;
|
|
1038
1072
|
}
|
|
1073
|
+
function detectPlatform() {
|
|
1074
|
+
if (os3.platform() === "darwin") {
|
|
1075
|
+
return { type: "launchd", pidFile: "ai.openclaw.gateway" };
|
|
1076
|
+
}
|
|
1077
|
+
return { type: "systemd", pidFile: "openclaw-gateway.service" };
|
|
1078
|
+
}
|
|
1039
1079
|
function generateToml(opts) {
|
|
1080
|
+
const platform3 = detectPlatform();
|
|
1040
1081
|
let toml = `# OpenClaw Aegis Configuration
|
|
1041
1082
|
# Generated by 'aegis init'
|
|
1042
1083
|
|
|
1043
1084
|
[gateway]
|
|
1044
1085
|
configPath = "~/.openclaw/openclaw.json"
|
|
1045
|
-
pidFile = "
|
|
1086
|
+
pidFile = "${platform3.pidFile}"
|
|
1046
1087
|
port = ${opts.port}
|
|
1047
1088
|
logPath = "~/.openclaw/logs/gateway.log"
|
|
1048
1089
|
healthEndpoint = "/health"
|
|
@@ -1069,7 +1110,7 @@ enabled = true
|
|
|
1069
1110
|
countdownMs = 30000
|
|
1070
1111
|
|
|
1071
1112
|
[platform]
|
|
1072
|
-
type = "
|
|
1113
|
+
type = "${platform3.type}"
|
|
1073
1114
|
`;
|
|
1074
1115
|
if (opts.channels.length > 0) {
|
|
1075
1116
|
toml += "\n[alerts]\n";
|
|
@@ -1089,7 +1130,6 @@ type = "systemd"
|
|
|
1089
1130
|
return toml;
|
|
1090
1131
|
}
|
|
1091
1132
|
var initCommand = new import_commander3.Command("init").description("Interactive setup wizard \u2014 configure Aegis for your gateway").option("--auto", "Auto-detect everything, no prompts").action(async (opts) => {
|
|
1092
|
-
const configDir = getConfigDir();
|
|
1093
1133
|
const configFile = expandHome(DEFAULT_CONFIG_PATH);
|
|
1094
1134
|
if (fs8.existsSync(configFile) && !opts.auto) {
|
|
1095
1135
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -1105,8 +1145,9 @@ var initCommand = new import_commander3.Command("init").description("Interactive
|
|
|
1105
1145
|
const channels = [];
|
|
1106
1146
|
if (opts.auto) {
|
|
1107
1147
|
console.log("Auto-detecting configuration...");
|
|
1148
|
+
const platform3 = detectPlatform();
|
|
1108
1149
|
console.log(` Gateway port: ${detectedPort}`);
|
|
1109
|
-
console.log(` PID source:
|
|
1150
|
+
console.log(` PID source: ${platform3.pidFile} (${platform3.type})`);
|
|
1110
1151
|
console.log(` Memory threshold: 768MB`);
|
|
1111
1152
|
} else {
|
|
1112
1153
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -1119,10 +1160,16 @@ var initCommand = new import_commander3.Command("init").description("Interactive
|
|
|
1119
1160
|
const memStr = await ask(rl, "Memory threshold (MB)", "768");
|
|
1120
1161
|
const memThreshold = parseInt(memStr, 10) || 768;
|
|
1121
1162
|
console.log("\n Alert Channels (out-of-band \u2014 never through the gateway)\n");
|
|
1122
|
-
console.log(
|
|
1163
|
+
console.log(
|
|
1164
|
+
" Supported: ntfy, telegram, whatsapp, slack, discord, email, pushover, webhook\n"
|
|
1165
|
+
);
|
|
1123
1166
|
let addMore = true;
|
|
1124
1167
|
while (addMore) {
|
|
1125
|
-
const channelType = await ask(
|
|
1168
|
+
const channelType = await ask(
|
|
1169
|
+
rl,
|
|
1170
|
+
"Add alert channel? (ntfy/telegram/whatsapp/slack/discord/email/pushover/webhook/skip)",
|
|
1171
|
+
"skip"
|
|
1172
|
+
);
|
|
1126
1173
|
if (channelType === "skip" || channelType === "") {
|
|
1127
1174
|
addMore = false;
|
|
1128
1175
|
continue;
|
|
@@ -1150,7 +1197,10 @@ var initCommand = new import_commander3.Command("init").description("Interactive
|
|
|
1150
1197
|
case "whatsapp": {
|
|
1151
1198
|
const phoneNumberId = await ask(rl, " WhatsApp Business phone number ID");
|
|
1152
1199
|
const accessToken = await ask(rl, " WhatsApp Cloud API access token");
|
|
1153
|
-
const recipientNumber = await ask(
|
|
1200
|
+
const recipientNumber = await ask(
|
|
1201
|
+
rl,
|
|
1202
|
+
" Recipient phone number (with country code, e.g. 61412345678)"
|
|
1203
|
+
);
|
|
1154
1204
|
if (phoneNumberId && accessToken && recipientNumber) {
|
|
1155
1205
|
channels.push({ type: "whatsapp", phoneNumberId, accessToken, recipientNumber });
|
|
1156
1206
|
console.log(" Added WhatsApp channel\n");
|
|
@@ -1163,7 +1213,10 @@ var initCommand = new import_commander3.Command("init").description("Interactive
|
|
|
1163
1213
|
const webhookUrl = await ask(rl, " Slack Incoming Webhook URL");
|
|
1164
1214
|
if (webhookUrl) {
|
|
1165
1215
|
const channel = await ask(rl, " Slack channel override (optional, e.g. #alerts)");
|
|
1166
|
-
const ch = {
|
|
1216
|
+
const ch = {
|
|
1217
|
+
type: "slack",
|
|
1218
|
+
webhookUrl
|
|
1219
|
+
};
|
|
1167
1220
|
if (channel) ch.channel = channel;
|
|
1168
1221
|
channels.push(ch);
|
|
1169
1222
|
console.log(" Added Slack channel\n");
|
|
@@ -1176,7 +1229,10 @@ var initCommand = new import_commander3.Command("init").description("Interactive
|
|
|
1176
1229
|
const webhookUrl = await ask(rl, " Discord Webhook URL");
|
|
1177
1230
|
if (webhookUrl) {
|
|
1178
1231
|
const username = await ask(rl, " Bot display name (optional)", "Aegis");
|
|
1179
|
-
const ch = {
|
|
1232
|
+
const ch = {
|
|
1233
|
+
type: "discord",
|
|
1234
|
+
webhookUrl
|
|
1235
|
+
};
|
|
1180
1236
|
if (username && username !== "Aegis") ch.username = username;
|
|
1181
1237
|
channels.push(ch);
|
|
1182
1238
|
console.log(" Added Discord channel\n");
|
|
@@ -1214,7 +1270,11 @@ var initCommand = new import_commander3.Command("init").description("Interactive
|
|
|
1214
1270
|
const userKey = await ask(rl, " Pushover user key");
|
|
1215
1271
|
if (apiToken && userKey) {
|
|
1216
1272
|
const device = await ask(rl, " Device name (optional)");
|
|
1217
|
-
const ch = {
|
|
1273
|
+
const ch = {
|
|
1274
|
+
type: "pushover",
|
|
1275
|
+
apiToken,
|
|
1276
|
+
userKey
|
|
1277
|
+
};
|
|
1218
1278
|
if (device) ch.device = device;
|
|
1219
1279
|
channels.push(ch);
|
|
1220
1280
|
console.log(" Added Pushover channel\n");
|
|
@@ -1254,7 +1314,9 @@ var initCommand = new import_commander3.Command("init").description("Interactive
|
|
|
1254
1314
|
const score2 = await monitor2.runAllProbes();
|
|
1255
1315
|
const passed2 = score2.probeResults.filter((p) => p.healthy).length;
|
|
1256
1316
|
const failed = score2.probeResults.filter((p) => !p.healthy);
|
|
1257
|
-
console.log(
|
|
1317
|
+
console.log(
|
|
1318
|
+
` Health: ${score2.band.toUpperCase()} (${passed2}/${score2.probeResults.length} probes passed)`
|
|
1319
|
+
);
|
|
1258
1320
|
if (failed.length > 0) {
|
|
1259
1321
|
for (const probe of failed) {
|
|
1260
1322
|
console.log(` - ${probe.name}: ${probe.message ?? "failed"}`);
|
|
@@ -1275,7 +1337,9 @@ var initCommand = new import_commander3.Command("init").description("Interactive
|
|
|
1275
1337
|
const monitor = new HealthMonitor(config);
|
|
1276
1338
|
const score = await monitor.runAllProbes();
|
|
1277
1339
|
const passed = score.probeResults.filter((p) => p.healthy).length;
|
|
1278
|
-
console.log(
|
|
1340
|
+
console.log(
|
|
1341
|
+
`Health: ${score.band.toUpperCase()} (${passed}/${score.probeResults.length} probes passed)`
|
|
1342
|
+
);
|
|
1279
1343
|
console.log("\nSetup complete.");
|
|
1280
1344
|
});
|
|
1281
1345
|
|
|
@@ -1380,9 +1444,9 @@ var NtfyProvider = class {
|
|
|
1380
1444
|
const response = await fetch(url, {
|
|
1381
1445
|
method: "POST",
|
|
1382
1446
|
headers: {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1447
|
+
Title: alert.title,
|
|
1448
|
+
Priority: String(this.config.priority),
|
|
1449
|
+
Tags: alertSeverityToTag(alert.severity)
|
|
1386
1450
|
},
|
|
1387
1451
|
body: alert.body
|
|
1388
1452
|
});
|
|
@@ -1502,7 +1566,7 @@ ${alert.body}`;
|
|
|
1502
1566
|
const response = await fetch(url, {
|
|
1503
1567
|
method: "POST",
|
|
1504
1568
|
headers: {
|
|
1505
|
-
|
|
1569
|
+
Authorization: `Bearer ${this.config.accessToken}`,
|
|
1506
1570
|
"Content-Type": "application/json"
|
|
1507
1571
|
},
|
|
1508
1572
|
body: JSON.stringify({
|
|
@@ -1662,13 +1726,15 @@ var DiscordProvider = class {
|
|
|
1662
1726
|
const start = Date.now();
|
|
1663
1727
|
const color = alert.severity === "critical" ? 15548997 : alert.severity === "warning" ? 16705372 : 5763719;
|
|
1664
1728
|
const payload = {
|
|
1665
|
-
embeds: [
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1729
|
+
embeds: [
|
|
1730
|
+
{
|
|
1731
|
+
title: alert.title,
|
|
1732
|
+
description: alert.body,
|
|
1733
|
+
color,
|
|
1734
|
+
timestamp: alert.timestamp,
|
|
1735
|
+
footer: { text: "OpenClaw Aegis" }
|
|
1736
|
+
}
|
|
1737
|
+
]
|
|
1672
1738
|
};
|
|
1673
1739
|
if (this.config.username) {
|
|
1674
1740
|
payload.username = this.config.username;
|
|
@@ -1860,29 +1926,31 @@ ${body}\r
|
|
|
1860
1926
|
plain.write("STARTTLS\r\n");
|
|
1861
1927
|
startTlsSent = true;
|
|
1862
1928
|
} else if (code === 220 && startTlsSent) {
|
|
1863
|
-
socket = tls.connect(
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1929
|
+
socket = tls.connect(
|
|
1930
|
+
{
|
|
1931
|
+
socket: plain,
|
|
1932
|
+
host: this.config.host,
|
|
1933
|
+
rejectUnauthorized: true
|
|
1934
|
+
},
|
|
1935
|
+
() => {
|
|
1936
|
+
let step2 = 0;
|
|
1937
|
+
let buffer2 = "";
|
|
1938
|
+
const cmds = [
|
|
1939
|
+
`EHLO aegis\r
|
|
1872
1940
|
`,
|
|
1873
|
-
|
|
1941
|
+
`AUTH LOGIN\r
|
|
1874
1942
|
`,
|
|
1875
|
-
|
|
1943
|
+
`${Buffer.from(this.config.username).toString("base64")}\r
|
|
1876
1944
|
`,
|
|
1877
|
-
|
|
1945
|
+
`${Buffer.from(this.config.password).toString("base64")}\r
|
|
1878
1946
|
`,
|
|
1879
|
-
|
|
1947
|
+
`MAIL FROM:<${this.config.from}>\r
|
|
1880
1948
|
`,
|
|
1881
|
-
|
|
1949
|
+
`RCPT TO:<${this.config.to}>\r
|
|
1882
1950
|
`,
|
|
1883
|
-
|
|
1951
|
+
`DATA\r
|
|
1884
1952
|
`,
|
|
1885
|
-
|
|
1953
|
+
`From: ${this.config.from}\r
|
|
1886
1954
|
To: ${this.config.to}\r
|
|
1887
1955
|
Subject: ${subject}\r
|
|
1888
1956
|
Content-Type: text/plain; charset=utf-8\r
|
|
@@ -1891,36 +1959,37 @@ X-Mailer: OpenClaw-Aegis\r
|
|
|
1891
1959
|
${body}\r
|
|
1892
1960
|
.\r
|
|
1893
1961
|
`,
|
|
1894
|
-
|
|
1962
|
+
`QUIT\r
|
|
1895
1963
|
`
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1964
|
+
];
|
|
1965
|
+
socket.write(cmds[0]);
|
|
1966
|
+
step2 = 1;
|
|
1967
|
+
socket.on("data", (d) => {
|
|
1968
|
+
buffer2 += d.toString();
|
|
1969
|
+
const ls = buffer2.split("\r\n");
|
|
1970
|
+
buffer2 = ls.pop() ?? "";
|
|
1971
|
+
for (const l of ls) {
|
|
1972
|
+
if (!l) continue;
|
|
1973
|
+
const c = parseInt(l.substring(0, 3), 10);
|
|
1974
|
+
if (l.charAt(3) === "-") continue;
|
|
1975
|
+
if (c >= 400) {
|
|
1976
|
+
socket.destroy();
|
|
1977
|
+
reject(new Error(`SMTP error: ${l}`));
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
if (step2 < cmds.length) {
|
|
1981
|
+
socket.write(cmds[step2]);
|
|
1982
|
+
step2++;
|
|
1983
|
+
}
|
|
1984
|
+
if (step2 >= cmds.length && c === 221) {
|
|
1985
|
+
socket.end();
|
|
1986
|
+
resolve();
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1915
1989
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
return;
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
});
|
|
1923
|
-
});
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
);
|
|
1924
1993
|
return;
|
|
1925
1994
|
}
|
|
1926
1995
|
}
|
|
@@ -2054,6 +2123,135 @@ var testAlertCommand = new import_commander4.Command("test-alert").description("
|
|
|
2054
2123
|
process.exit(result.sent ? 0 : 1);
|
|
2055
2124
|
});
|
|
2056
2125
|
|
|
2126
|
+
// src/cli/commands/incidents.ts
|
|
2127
|
+
var import_commander5 = require("commander");
|
|
2128
|
+
var fs9 = __toESM(require("fs"));
|
|
2129
|
+
var path3 = __toESM(require("path"));
|
|
2130
|
+
function parseIncident(filePath) {
|
|
2131
|
+
const id = path3.basename(filePath, ".jsonl");
|
|
2132
|
+
const lines = fs9.readFileSync(filePath, "utf-8").trim().split("\n").filter(Boolean);
|
|
2133
|
+
const events = [];
|
|
2134
|
+
for (const line of lines) {
|
|
2135
|
+
try {
|
|
2136
|
+
events.push(JSON.parse(line));
|
|
2137
|
+
} catch {
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
const started = events[0]?.timestamp ?? "unknown";
|
|
2141
|
+
const resolvedEvent = events.find((e) => e.type === "INCIDENT_RESOLVED");
|
|
2142
|
+
const unresolvedEvent = events.find((e) => e.type === "INCIDENT_UNRESOLVED");
|
|
2143
|
+
const resolved = !!resolvedEvent;
|
|
2144
|
+
let durationMs = null;
|
|
2145
|
+
const endEvent = resolvedEvent ?? unresolvedEvent;
|
|
2146
|
+
if (endEvent && events[0]) {
|
|
2147
|
+
durationMs = new Date(endEvent.timestamp).getTime() - new Date(events[0].timestamp).getTime();
|
|
2148
|
+
}
|
|
2149
|
+
return { id, started, resolved, events, durationMs };
|
|
2150
|
+
}
|
|
2151
|
+
function formatDuration(ms) {
|
|
2152
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
2153
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
2154
|
+
return `${(ms / 6e4).toFixed(1)}m`;
|
|
2155
|
+
}
|
|
2156
|
+
var incidentsCommand = new import_commander5.Command("incidents").description("Browse past incident logs").option("--json", "Output as JSON").option("--last <n>", "Show last N incidents", "10").argument("[incident-id]", "Show details for a specific incident").action((incidentId, opts) => {
|
|
2157
|
+
const incidentsDir = expandHome("~/.openclaw/aegis/incidents");
|
|
2158
|
+
if (!fs9.existsSync(incidentsDir)) {
|
|
2159
|
+
console.log("No incidents recorded yet.");
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
const files = fs9.readdirSync(incidentsDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
|
|
2163
|
+
if (files.length === 0) {
|
|
2164
|
+
console.log("No incidents recorded yet.");
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
if (incidentId) {
|
|
2168
|
+
const file = files.find((f) => f.startsWith(incidentId));
|
|
2169
|
+
if (!file) {
|
|
2170
|
+
console.log(`Incident "${incidentId}" not found.`);
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
const incident = parseIncident(path3.join(incidentsDir, file));
|
|
2174
|
+
if (opts.json) {
|
|
2175
|
+
process.stdout.write(JSON.stringify(incident, null, 2) + "\n");
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
const status = incident.resolved ? "\x1B[32mRESOLVED\x1B[0m" : "\x1B[31mUNRESOLVED\x1B[0m";
|
|
2179
|
+
const duration = incident.durationMs !== null ? formatDuration(incident.durationMs) : "ongoing";
|
|
2180
|
+
console.log(`
|
|
2181
|
+
Incident: ${incident.id}`);
|
|
2182
|
+
console.log(`Status: ${status}`);
|
|
2183
|
+
console.log(`Started: ${incident.started}`);
|
|
2184
|
+
console.log(`Duration: ${duration}`);
|
|
2185
|
+
console.log(`
|
|
2186
|
+
Timeline:
|
|
2187
|
+
`);
|
|
2188
|
+
for (const event of incident.events) {
|
|
2189
|
+
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
2190
|
+
const icon = eventIcon(event.type);
|
|
2191
|
+
const detail = formatEventDetail(event);
|
|
2192
|
+
console.log(` ${time} ${icon} ${event.type}${detail}`);
|
|
2193
|
+
}
|
|
2194
|
+
console.log();
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
const limit = parseInt(opts.last, 10) || 10;
|
|
2198
|
+
const incidents = files.slice(0, limit).map((f) => parseIncident(path3.join(incidentsDir, f)));
|
|
2199
|
+
if (opts.json) {
|
|
2200
|
+
process.stdout.write(JSON.stringify(incidents, null, 2) + "\n");
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
const resolved = incidents.filter((i) => i.resolved).length;
|
|
2204
|
+
const unresolved = incidents.length - resolved;
|
|
2205
|
+
console.log(
|
|
2206
|
+
`
|
|
2207
|
+
${incidents.length} incident(s) \u2014 ${resolved} resolved, ${unresolved} unresolved
|
|
2208
|
+
`
|
|
2209
|
+
);
|
|
2210
|
+
for (const inc of incidents) {
|
|
2211
|
+
const icon = inc.resolved ? "\x1B[32m+\x1B[0m" : "\x1B[31m-\x1B[0m";
|
|
2212
|
+
const duration = inc.durationMs !== null ? formatDuration(inc.durationMs) : "ongoing";
|
|
2213
|
+
const eventCount = inc.events.length;
|
|
2214
|
+
const date = new Date(inc.started).toLocaleString();
|
|
2215
|
+
console.log(` ${icon} ${inc.id} ${date} ${duration} (${eventCount} events)`);
|
|
2216
|
+
}
|
|
2217
|
+
console.log(`
|
|
2218
|
+
Run 'aegis incidents <id>' for details.
|
|
2219
|
+
`);
|
|
2220
|
+
});
|
|
2221
|
+
function eventIcon(type) {
|
|
2222
|
+
switch (type) {
|
|
2223
|
+
case "INCIDENT_START":
|
|
2224
|
+
return "\x1B[31m>\x1B[0m";
|
|
2225
|
+
case "L1_ATTEMPT":
|
|
2226
|
+
return "\x1B[33m~\x1B[0m";
|
|
2227
|
+
case "L1_SUCCESS":
|
|
2228
|
+
return "\x1B[32m+\x1B[0m";
|
|
2229
|
+
case "L2_ATTEMPT":
|
|
2230
|
+
return "\x1B[33m~\x1B[0m";
|
|
2231
|
+
case "L2_SUCCESS":
|
|
2232
|
+
return "\x1B[32m+\x1B[0m";
|
|
2233
|
+
case "L4_ALERT":
|
|
2234
|
+
return "\x1B[31m!\x1B[0m";
|
|
2235
|
+
case "INCIDENT_RESOLVED":
|
|
2236
|
+
return "\x1B[32m+\x1B[0m";
|
|
2237
|
+
case "INCIDENT_UNRESOLVED":
|
|
2238
|
+
return "\x1B[31mx\x1B[0m";
|
|
2239
|
+
case "DEAD_MAN_SWITCH_ROLLBACK":
|
|
2240
|
+
return "\x1B[33m<\x1B[0m";
|
|
2241
|
+
case "CIRCUIT_BREAKER_TRIPPED":
|
|
2242
|
+
return "\x1B[31m#\x1B[0m";
|
|
2243
|
+
default:
|
|
2244
|
+
return " ";
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
function formatEventDetail(event) {
|
|
2248
|
+
const d = event.data;
|
|
2249
|
+
if (d.attempt) return ` (attempt ${String(d.attempt)})`;
|
|
2250
|
+
if (d.pattern) return ` \u2014 ${String(d.pattern)}`;
|
|
2251
|
+
if (d.reason) return ` \u2014 ${String(d.reason)}`;
|
|
2252
|
+
return "";
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2057
2255
|
// src/cli/index.ts
|
|
2058
2256
|
function getVersion() {
|
|
2059
2257
|
try {
|
|
@@ -2063,11 +2261,12 @@ function getVersion() {
|
|
|
2063
2261
|
return "unknown";
|
|
2064
2262
|
}
|
|
2065
2263
|
}
|
|
2066
|
-
var program = new
|
|
2264
|
+
var program = new import_commander6.Command();
|
|
2067
2265
|
program.name("aegis").description("OpenClaw Aegis \u2014 self-healing sidecar for the OpenClaw gateway").version(getVersion());
|
|
2068
2266
|
program.addCommand(initCommand);
|
|
2069
2267
|
program.addCommand(statusCommand);
|
|
2070
2268
|
program.addCommand(checkCommand);
|
|
2071
2269
|
program.addCommand(testAlertCommand);
|
|
2270
|
+
program.addCommand(incidentsCommand);
|
|
2072
2271
|
program.parse(process.argv);
|
|
2073
2272
|
//# sourceMappingURL=index.js.map
|