@chrysb/alphaclaw 0.2.2 → 0.3.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/bin/alphaclaw.js +79 -0
- package/lib/public/css/shell.css +57 -2
- package/lib/public/css/theme.css +184 -0
- package/lib/public/js/app.js +330 -89
- package/lib/public/js/components/action-button.js +92 -0
- package/lib/public/js/components/channels.js +16 -7
- package/lib/public/js/components/confirm-dialog.js +25 -19
- package/lib/public/js/components/credentials-modal.js +32 -23
- package/lib/public/js/components/device-pairings.js +15 -2
- package/lib/public/js/components/envars.js +22 -65
- package/lib/public/js/components/features.js +1 -1
- package/lib/public/js/components/gateway.js +139 -32
- package/lib/public/js/components/global-restart-banner.js +31 -0
- package/lib/public/js/components/google.js +9 -9
- package/lib/public/js/components/icons.js +19 -0
- package/lib/public/js/components/info-tooltip.js +18 -0
- package/lib/public/js/components/loading-spinner.js +32 -0
- package/lib/public/js/components/modal-shell.js +42 -0
- package/lib/public/js/components/models.js +34 -29
- package/lib/public/js/components/onboarding/welcome-form-step.js +45 -32
- package/lib/public/js/components/onboarding/welcome-pairing-step.js +2 -2
- package/lib/public/js/components/onboarding/welcome-setup-step.js +7 -24
- package/lib/public/js/components/page-header.js +13 -0
- package/lib/public/js/components/pairings.js +15 -2
- package/lib/public/js/components/providers.js +216 -142
- package/lib/public/js/components/scope-picker.js +1 -1
- package/lib/public/js/components/secret-input.js +1 -0
- package/lib/public/js/components/telegram-workspace.js +37 -49
- package/lib/public/js/components/toast.js +34 -5
- package/lib/public/js/components/toggle-switch.js +25 -0
- package/lib/public/js/components/update-action-button.js +13 -53
- package/lib/public/js/components/watchdog-tab.js +312 -0
- package/lib/public/js/components/webhooks.js +981 -0
- package/lib/public/js/components/welcome.js +2 -1
- package/lib/public/js/lib/api.js +102 -1
- package/lib/public/js/lib/model-config.js +0 -5
- package/lib/public/login.html +1 -0
- package/lib/public/setup.html +1 -0
- package/lib/server/alphaclaw-version.js +5 -3
- package/lib/server/constants.js +33 -0
- package/lib/server/discord-api.js +48 -0
- package/lib/server/gateway.js +64 -4
- package/lib/server/log-writer.js +102 -0
- package/lib/server/onboarding/github.js +21 -1
- package/lib/server/openclaw-version.js +2 -6
- package/lib/server/restart-required-state.js +86 -0
- package/lib/server/routes/auth.js +9 -4
- package/lib/server/routes/proxy.js +12 -14
- package/lib/server/routes/system.js +61 -15
- package/lib/server/routes/telegram.js +17 -48
- package/lib/server/routes/watchdog.js +68 -0
- package/lib/server/routes/webhooks.js +214 -0
- package/lib/server/telegram-api.js +11 -0
- package/lib/server/watchdog-db.js +148 -0
- package/lib/server/watchdog-notify.js +93 -0
- package/lib/server/watchdog.js +585 -0
- package/lib/server/webhook-middleware.js +195 -0
- package/lib/server/webhooks-db.js +265 -0
- package/lib/server/webhooks.js +238 -0
- package/lib/server.js +119 -4
- package/lib/setup/core-prompts/AGENTS.md +84 -0
- package/lib/setup/core-prompts/TOOLS.md +13 -0
- package/lib/setup/core-prompts/UI-DRY-OPPORTUNITIES.md +50 -0
- package/lib/setup/gitignore +2 -0
- package/package.json +2 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { DatabaseSync } = require("node:sqlite");
|
|
4
|
+
|
|
5
|
+
let db = null;
|
|
6
|
+
let pruneTimer = null;
|
|
7
|
+
|
|
8
|
+
const kDefaultLimit = 20;
|
|
9
|
+
const kMaxLimit = 200;
|
|
10
|
+
const kPruneIntervalMs = 12 * 60 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
const ensureDb = () => {
|
|
13
|
+
if (!db) throw new Error("Watchdog DB not initialized");
|
|
14
|
+
return db;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const createSchema = (database) => {
|
|
18
|
+
database.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS watchdog_events (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
event_type TEXT NOT NULL,
|
|
22
|
+
source TEXT NOT NULL,
|
|
23
|
+
status TEXT NOT NULL,
|
|
24
|
+
details TEXT,
|
|
25
|
+
correlation_id TEXT,
|
|
26
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
27
|
+
);
|
|
28
|
+
`);
|
|
29
|
+
database.exec(`
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_watchdog_events_ts
|
|
31
|
+
ON watchdog_events(created_at DESC);
|
|
32
|
+
`);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const initWatchdogDb = ({ rootDir, pruneDays = 30 }) => {
|
|
36
|
+
const dbDir = path.join(rootDir, "db");
|
|
37
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
38
|
+
const dbPath = path.join(dbDir, "watchdog.db");
|
|
39
|
+
db = new DatabaseSync(dbPath);
|
|
40
|
+
createSchema(db);
|
|
41
|
+
pruneWatchdogEvents(pruneDays);
|
|
42
|
+
if (pruneTimer) clearInterval(pruneTimer);
|
|
43
|
+
pruneTimer = setInterval(() => {
|
|
44
|
+
try {
|
|
45
|
+
pruneWatchdogEvents(pruneDays);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(`[watchdog-db] prune error: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
}, kPruneIntervalMs);
|
|
50
|
+
if (typeof pruneTimer.unref === "function") pruneTimer.unref();
|
|
51
|
+
return { path: dbPath };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const insertWatchdogEvent = ({
|
|
55
|
+
eventType,
|
|
56
|
+
source,
|
|
57
|
+
status,
|
|
58
|
+
details = null,
|
|
59
|
+
correlationId = "",
|
|
60
|
+
}) => {
|
|
61
|
+
const database = ensureDb();
|
|
62
|
+
const stmt = database.prepare(`
|
|
63
|
+
INSERT INTO watchdog_events (
|
|
64
|
+
event_type,
|
|
65
|
+
source,
|
|
66
|
+
status,
|
|
67
|
+
details,
|
|
68
|
+
correlation_id
|
|
69
|
+
) VALUES (
|
|
70
|
+
$event_type,
|
|
71
|
+
$source,
|
|
72
|
+
$status,
|
|
73
|
+
$details,
|
|
74
|
+
$correlation_id
|
|
75
|
+
)
|
|
76
|
+
`);
|
|
77
|
+
const result = stmt.run({
|
|
78
|
+
$event_type: String(eventType || ""),
|
|
79
|
+
$source: String(source || ""),
|
|
80
|
+
$status: String(status || "failed"),
|
|
81
|
+
$details:
|
|
82
|
+
details == null
|
|
83
|
+
? null
|
|
84
|
+
: typeof details === "string"
|
|
85
|
+
? details
|
|
86
|
+
: JSON.stringify(details),
|
|
87
|
+
$correlation_id: String(correlationId || ""),
|
|
88
|
+
});
|
|
89
|
+
return Number(result.lastInsertRowid || 0);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const getRecentEvents = ({ limit = kDefaultLimit, includeRoutine = false } = {}) => {
|
|
93
|
+
const database = ensureDb();
|
|
94
|
+
const safeLimit = Math.max(
|
|
95
|
+
1,
|
|
96
|
+
Math.min(Number.parseInt(String(limit || kDefaultLimit), 10) || kDefaultLimit, kMaxLimit),
|
|
97
|
+
);
|
|
98
|
+
const whereClause = includeRoutine
|
|
99
|
+
? ""
|
|
100
|
+
: "WHERE NOT (event_type = 'health_check' AND status = 'ok')";
|
|
101
|
+
const rows = database
|
|
102
|
+
.prepare(`
|
|
103
|
+
SELECT id, event_type, source, status, details, correlation_id, created_at
|
|
104
|
+
FROM watchdog_events
|
|
105
|
+
${whereClause}
|
|
106
|
+
ORDER BY created_at DESC
|
|
107
|
+
LIMIT $limit
|
|
108
|
+
`)
|
|
109
|
+
.all({ $limit: safeLimit });
|
|
110
|
+
const mapped = rows.map((row) => {
|
|
111
|
+
let parsedDetails = row.details;
|
|
112
|
+
if (typeof row.details === "string" && row.details) {
|
|
113
|
+
try {
|
|
114
|
+
parsedDetails = JSON.parse(row.details);
|
|
115
|
+
} catch {}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
id: row.id,
|
|
119
|
+
eventType: row.event_type,
|
|
120
|
+
source: row.source,
|
|
121
|
+
status: row.status,
|
|
122
|
+
details: parsedDetails,
|
|
123
|
+
correlationId: row.correlation_id || "",
|
|
124
|
+
createdAt: row.created_at,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
return mapped;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const pruneWatchdogEvents = (days = 30) => {
|
|
131
|
+
const database = ensureDb();
|
|
132
|
+
const safeDays = Math.max(1, Number.parseInt(String(days || 30), 10) || 30);
|
|
133
|
+
const modifier = `-${safeDays} days`;
|
|
134
|
+
const result = database
|
|
135
|
+
.prepare(`
|
|
136
|
+
DELETE FROM watchdog_events
|
|
137
|
+
WHERE created_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', $modifier)
|
|
138
|
+
`)
|
|
139
|
+
.run({ $modifier: modifier });
|
|
140
|
+
return Number(result.changes || 0);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
initWatchdogDb,
|
|
145
|
+
insertWatchdogEvent,
|
|
146
|
+
getRecentEvents,
|
|
147
|
+
pruneWatchdogEvents,
|
|
148
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { OPENCLAW_DIR } = require("./constants");
|
|
4
|
+
|
|
5
|
+
const getPairedIds = (channel) => {
|
|
6
|
+
const safeChannel = String(channel || "").trim().toLowerCase();
|
|
7
|
+
if (!safeChannel) return [];
|
|
8
|
+
const credentialsDir = path.join(OPENCLAW_DIR, "credentials");
|
|
9
|
+
if (!fs.existsSync(credentialsDir)) return [];
|
|
10
|
+
const ids = new Set();
|
|
11
|
+
try {
|
|
12
|
+
const files = fs
|
|
13
|
+
.readdirSync(credentialsDir)
|
|
14
|
+
.filter(
|
|
15
|
+
(fileName) =>
|
|
16
|
+
fileName.startsWith(`${safeChannel}-`) && fileName.endsWith("-allowFrom.json"),
|
|
17
|
+
);
|
|
18
|
+
for (const fileName of files) {
|
|
19
|
+
const filePath = path.join(credentialsDir, fileName);
|
|
20
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
const allowFrom = Array.isArray(parsed?.allowFrom) ? parsed.allowFrom : [];
|
|
23
|
+
for (const id of allowFrom) {
|
|
24
|
+
if (id == null) continue;
|
|
25
|
+
const value = String(id).trim();
|
|
26
|
+
if (!value) continue;
|
|
27
|
+
ids.add(value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(`[watchdog] could not resolve ${safeChannel} allowFrom IDs: ${err.message}`);
|
|
32
|
+
}
|
|
33
|
+
return Array.from(ids);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const formatDiscordMessage = (message) => String(message || "");
|
|
37
|
+
|
|
38
|
+
const createWatchdogNotifier = ({ telegramApi, discordApi }) => {
|
|
39
|
+
const notify = async (message) => {
|
|
40
|
+
const summary = {
|
|
41
|
+
telegram: { sent: 0, failed: 0, skipped: false, targets: 0 },
|
|
42
|
+
discord: { sent: 0, failed: 0, skipped: false, targets: 0 },
|
|
43
|
+
};
|
|
44
|
+
const telegramTargets = getPairedIds("telegram");
|
|
45
|
+
summary.telegram.targets = telegramTargets.length;
|
|
46
|
+
if (!telegramApi?.sendMessage || !process.env.TELEGRAM_BOT_TOKEN || telegramTargets.length === 0) {
|
|
47
|
+
summary.telegram.skipped = true;
|
|
48
|
+
} else {
|
|
49
|
+
for (const chatId of telegramTargets) {
|
|
50
|
+
try {
|
|
51
|
+
await telegramApi.sendMessage(chatId, String(message || ""), {
|
|
52
|
+
parseMode: "Markdown",
|
|
53
|
+
});
|
|
54
|
+
summary.telegram.sent += 1;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
summary.telegram.failed += 1;
|
|
57
|
+
console.error(`[watchdog] telegram notification failed for ${chatId}: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const discordTargets = getPairedIds("discord");
|
|
63
|
+
summary.discord.targets = discordTargets.length;
|
|
64
|
+
if (!discordApi?.sendDirectMessage || !process.env.DISCORD_BOT_TOKEN || discordTargets.length === 0) {
|
|
65
|
+
summary.discord.skipped = true;
|
|
66
|
+
} else {
|
|
67
|
+
const discordMessage = formatDiscordMessage(message);
|
|
68
|
+
for (const userId of discordTargets) {
|
|
69
|
+
try {
|
|
70
|
+
await discordApi.sendDirectMessage(userId, discordMessage);
|
|
71
|
+
summary.discord.sent += 1;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
summary.discord.failed += 1;
|
|
74
|
+
console.error(`[watchdog] discord notification failed for ${userId}: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const sent = summary.telegram.sent + summary.discord.sent;
|
|
80
|
+
const failed = summary.telegram.failed + summary.discord.failed;
|
|
81
|
+
return {
|
|
82
|
+
ok: sent > 0,
|
|
83
|
+
sent,
|
|
84
|
+
failed,
|
|
85
|
+
channels: summary,
|
|
86
|
+
...(sent === 0 ? { reason: "no_channels_delivered" } : {}),
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return { notify };
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
module.exports = { createWatchdogNotifier };
|