@chrysb/alphaclaw 0.8.3-beta.5 → 0.8.4-beta.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/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +1274 -1168
- package/lib/public/js/components/agents-tab/create-channel-modal.js +259 -59
- package/lib/public/js/components/modal-shell.js +1 -1
- package/lib/public/js/lib/channel-provider-availability.js +1 -2
- package/lib/public/js/lib/model-config.js +1 -1
- package/lib/server/agents/channels.js +1 -4
- package/lib/server/exec-defaults-config.js +172 -0
- package/lib/server/init/server-lifecycle.js +2 -0
- package/lib/server/onboarding/index.js +5 -0
- package/lib/server/routes/nodes.js +9 -23
- package/lib/server/slack-api.js +72 -0
- package/lib/server/startup.js +8 -0
- package/lib/server/watchdog-notify.js +172 -55
- package/lib/server.js +14 -1
- package/package.json +2 -2
- package/patches/openclaw+2026.4.1.patch +13 -0
- package/lib/setup/core-prompts/UI-DRY-OPPORTUNITIES.md +0 -50
- package/patches/openclaw+2026.3.28.patch +0 -13
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
1
|
const crypto = require("crypto");
|
|
3
2
|
const { parseJsonObjectFromNoisyOutput } = require("../utils/json");
|
|
4
3
|
const { quoteShellArg } = require("../utils/shell");
|
|
4
|
+
const {
|
|
5
|
+
readExecApprovalsConfig,
|
|
6
|
+
writeExecApprovalsConfig,
|
|
7
|
+
} = require("../exec-defaults-config");
|
|
5
8
|
|
|
6
9
|
const kAllowedExecHosts = new Set(["gateway", "node"]);
|
|
7
10
|
const kAllowedExecSecurity = new Set(["deny", "allowlist", "full"]);
|
|
@@ -81,23 +84,6 @@ const parseNodeBrowserStatus = (stdout) => {
|
|
|
81
84
|
return decodedResult && typeof decodedResult === "object" ? decodedResult : null;
|
|
82
85
|
};
|
|
83
86
|
|
|
84
|
-
const readExecApprovalsFile = ({ fsModule, openclawDir }) => {
|
|
85
|
-
const filePath = path.join(openclawDir, "exec-approvals.json");
|
|
86
|
-
try {
|
|
87
|
-
const raw = fsModule.readFileSync(filePath, "utf8");
|
|
88
|
-
const parsed = JSON.parse(raw);
|
|
89
|
-
return parsed && typeof parsed === "object" ? parsed : { version: 1 };
|
|
90
|
-
} catch {
|
|
91
|
-
return { version: 1 };
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const writeExecApprovalsFile = ({ fsModule, openclawDir, file }) => {
|
|
96
|
-
const filePath = path.join(openclawDir, "exec-approvals.json");
|
|
97
|
-
fsModule.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
98
|
-
fsModule.writeFileSync(filePath, JSON.stringify(file, null, 2) + "\n", "utf8");
|
|
99
|
-
};
|
|
100
|
-
|
|
101
87
|
const ensureWildcardAgent = (file) => {
|
|
102
88
|
const agents = file.agents && typeof file.agents === "object" ? file.agents : {};
|
|
103
89
|
const wildcard =
|
|
@@ -372,7 +358,7 @@ const registerNodeRoutes = ({
|
|
|
372
358
|
|
|
373
359
|
app.get("/api/nodes/exec-approvals", (_req, res) => {
|
|
374
360
|
const approvals = ensureWildcardAgent(
|
|
375
|
-
|
|
361
|
+
readExecApprovalsConfig({ fsModule, openclawDir }),
|
|
376
362
|
);
|
|
377
363
|
const allowlist = approvals?.agents?.["*"]?.allowlist || [];
|
|
378
364
|
return res.json({
|
|
@@ -388,7 +374,7 @@ const registerNodeRoutes = ({
|
|
|
388
374
|
return res.status(400).json({ ok: false, error: "pattern is required" });
|
|
389
375
|
}
|
|
390
376
|
const approvals = ensureWildcardAgent(
|
|
391
|
-
|
|
377
|
+
readExecApprovalsConfig({ fsModule, openclawDir }),
|
|
392
378
|
);
|
|
393
379
|
const allowlist = approvals.agents["*"].allowlist;
|
|
394
380
|
const existing = allowlist.find(
|
|
@@ -403,7 +389,7 @@ const registerNodeRoutes = ({
|
|
|
403
389
|
lastUsedAt: Date.now(),
|
|
404
390
|
};
|
|
405
391
|
approvals.agents["*"].allowlist = [...allowlist, entry];
|
|
406
|
-
|
|
392
|
+
writeExecApprovalsConfig({ fsModule, openclawDir, file: approvals });
|
|
407
393
|
return res.json({ ok: true, entry });
|
|
408
394
|
});
|
|
409
395
|
|
|
@@ -413,7 +399,7 @@ const registerNodeRoutes = ({
|
|
|
413
399
|
return res.status(400).json({ ok: false, error: "id is required" });
|
|
414
400
|
}
|
|
415
401
|
const approvals = ensureWildcardAgent(
|
|
416
|
-
|
|
402
|
+
readExecApprovalsConfig({ fsModule, openclawDir }),
|
|
417
403
|
);
|
|
418
404
|
const allowlist = approvals.agents["*"].allowlist;
|
|
419
405
|
const nextAllowlist = allowlist.filter((entry) => String(entry?.id || "") !== id);
|
|
@@ -421,7 +407,7 @@ const registerNodeRoutes = ({
|
|
|
421
407
|
return res.status(404).json({ ok: false, error: "Allowlist entry not found" });
|
|
422
408
|
}
|
|
423
409
|
approvals.agents["*"].allowlist = nextAllowlist;
|
|
424
|
-
|
|
410
|
+
writeExecApprovalsConfig({ fsModule, openclawDir, file: approvals });
|
|
425
411
|
return res.json({ ok: true });
|
|
426
412
|
});
|
|
427
413
|
};
|
package/lib/server/slack-api.js
CHANGED
|
@@ -233,6 +233,72 @@ const createSlackApi = (getToken) => {
|
|
|
233
233
|
});
|
|
234
234
|
};
|
|
235
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Update an existing message
|
|
238
|
+
* @param {string} channel - Channel ID
|
|
239
|
+
* @param {string} timestamp - Message timestamp
|
|
240
|
+
* @param {string} text - New message text
|
|
241
|
+
* @returns {Promise<object>} Update response
|
|
242
|
+
* @requires chat:write OAuth scope
|
|
243
|
+
*/
|
|
244
|
+
const updateMessage = (channel, timestamp, text) => {
|
|
245
|
+
return call("chat.update", {
|
|
246
|
+
channel,
|
|
247
|
+
ts: timestamp,
|
|
248
|
+
text: String(text || ""),
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Delete a message
|
|
254
|
+
* @param {string} channel - Channel ID
|
|
255
|
+
* @param {string} timestamp - Message timestamp
|
|
256
|
+
* @requires chat:write OAuth scope
|
|
257
|
+
*/
|
|
258
|
+
const deleteMessage = (channel, timestamp) => {
|
|
259
|
+
return call("chat.delete", { channel, ts: timestamp });
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Pin a message to a channel
|
|
264
|
+
* @param {string} channel - Channel ID
|
|
265
|
+
* @param {string} timestamp - Message timestamp
|
|
266
|
+
* @requires pins:write OAuth scope
|
|
267
|
+
*/
|
|
268
|
+
const pinMessage = (channel, timestamp) => {
|
|
269
|
+
return call("pins.add", { channel, timestamp });
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Unpin a message from a channel
|
|
274
|
+
* @param {string} channel - Channel ID
|
|
275
|
+
* @param {string} timestamp - Message timestamp
|
|
276
|
+
* @requires pins:write OAuth scope
|
|
277
|
+
*/
|
|
278
|
+
const unpinMessage = (channel, timestamp) => {
|
|
279
|
+
return call("pins.remove", { channel, timestamp });
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get user information
|
|
284
|
+
* @param {string} userId - User ID
|
|
285
|
+
* @returns {Promise<object>} User info (name, real_name, email, etc.)
|
|
286
|
+
* @requires users:read OAuth scope
|
|
287
|
+
*/
|
|
288
|
+
const getUserInfo = (userId) => {
|
|
289
|
+
return call("users.info", { user: userId });
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get channel information
|
|
294
|
+
* @param {string} channelId - Channel ID
|
|
295
|
+
* @returns {Promise<object>} Channel info (name, topic, purpose, etc.)
|
|
296
|
+
* @requires channels:read or groups:read OAuth scope (depending on channel type)
|
|
297
|
+
*/
|
|
298
|
+
const getChannelInfo = (channelId) => {
|
|
299
|
+
return call("conversations.info", { channel: channelId });
|
|
300
|
+
};
|
|
301
|
+
|
|
236
302
|
return {
|
|
237
303
|
authTest,
|
|
238
304
|
postMessage,
|
|
@@ -241,6 +307,12 @@ const createSlackApi = (getToken) => {
|
|
|
241
307
|
removeReaction,
|
|
242
308
|
uploadFile,
|
|
243
309
|
uploadTextSnippet,
|
|
310
|
+
updateMessage,
|
|
311
|
+
deleteMessage,
|
|
312
|
+
pinMessage,
|
|
313
|
+
unpinMessage,
|
|
314
|
+
getUserInfo,
|
|
315
|
+
getChannelInfo,
|
|
244
316
|
};
|
|
245
317
|
};
|
|
246
318
|
|
package/lib/server/startup.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const runOnboardedBootSequence = ({
|
|
2
|
+
ensureManagedExecDefaults,
|
|
2
3
|
ensureUsageTrackerPluginConfig,
|
|
3
4
|
doSyncPromptFiles,
|
|
4
5
|
reloadEnv,
|
|
@@ -10,6 +11,13 @@ const runOnboardedBootSequence = ({
|
|
|
10
11
|
watchdog,
|
|
11
12
|
gmailWatchService,
|
|
12
13
|
}) => {
|
|
14
|
+
try {
|
|
15
|
+
ensureManagedExecDefaults();
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error(
|
|
18
|
+
`[alphaclaw] Failed to ensure managed exec defaults on boot: ${error.message}`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
13
21
|
try {
|
|
14
22
|
ensureUsageTrackerPluginConfig();
|
|
15
23
|
} catch (error) {
|
|
@@ -1,35 +1,95 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { OPENCLAW_DIR } = require("./constants");
|
|
4
|
+
const { createSlackApi } = require("./slack-api");
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
+
const kSlackBotEnvKey = "SLACK_BOT_TOKEN";
|
|
7
|
+
|
|
8
|
+
const normalizeAccountId = (value) =>
|
|
9
|
+
String(value || "").trim().toLowerCase() || "default";
|
|
10
|
+
|
|
11
|
+
const resolveCredentialPairingAccountId = ({ channel, fileName }) => {
|
|
12
|
+
const prefix = `${String(channel || "").trim().toLowerCase()}-`;
|
|
13
|
+
const suffix = "-allowFrom.json";
|
|
14
|
+
const rawFileName = String(fileName || "").trim();
|
|
15
|
+
if (!rawFileName.startsWith(prefix) || !rawFileName.endsWith(suffix)) {
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
return normalizeAccountId(rawFileName.slice(prefix.length, -suffix.length));
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const deriveSlackBotEnvKey = (accountId = "default") => {
|
|
22
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
23
|
+
if (normalizedAccountId === "default") return kSlackBotEnvKey;
|
|
24
|
+
return `${kSlackBotEnvKey}_${normalizedAccountId.replace(/-/g, "_").toUpperCase()}`;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getPairedTargetsByAccount = ({
|
|
28
|
+
channel,
|
|
29
|
+
fsImpl = fs,
|
|
30
|
+
openclawDir = OPENCLAW_DIR,
|
|
31
|
+
}) => {
|
|
6
32
|
const safeChannel = String(channel || "").trim().toLowerCase();
|
|
7
|
-
if (!safeChannel) return
|
|
8
|
-
const credentialsDir = path.join(
|
|
9
|
-
if (!
|
|
10
|
-
const
|
|
33
|
+
if (!safeChannel) return new Map();
|
|
34
|
+
const credentialsDir = path.join(openclawDir, "credentials");
|
|
35
|
+
if (!fsImpl.existsSync(credentialsDir)) return new Map();
|
|
36
|
+
const idsByAccount = new Map();
|
|
11
37
|
try {
|
|
12
|
-
const files =
|
|
38
|
+
const files = fsImpl
|
|
13
39
|
.readdirSync(credentialsDir)
|
|
14
40
|
.filter(
|
|
15
41
|
(fileName) =>
|
|
16
42
|
fileName.startsWith(`${safeChannel}-`) && fileName.endsWith("-allowFrom.json"),
|
|
17
43
|
);
|
|
18
44
|
for (const fileName of files) {
|
|
45
|
+
const accountId = resolveCredentialPairingAccountId({
|
|
46
|
+
channel: safeChannel,
|
|
47
|
+
fileName,
|
|
48
|
+
});
|
|
49
|
+
if (!accountId) continue;
|
|
19
50
|
const filePath = path.join(credentialsDir, fileName);
|
|
20
|
-
const raw =
|
|
51
|
+
const raw = fsImpl.readFileSync(filePath, "utf8");
|
|
21
52
|
const parsed = JSON.parse(raw);
|
|
22
53
|
const allowFrom = Array.isArray(parsed?.allowFrom) ? parsed.allowFrom : [];
|
|
54
|
+
const ids =
|
|
55
|
+
idsByAccount.get(accountId) instanceof Set
|
|
56
|
+
? idsByAccount.get(accountId)
|
|
57
|
+
: new Set();
|
|
23
58
|
for (const id of allowFrom) {
|
|
24
59
|
if (id == null) continue;
|
|
25
60
|
const value = String(id).trim();
|
|
26
61
|
if (!value) continue;
|
|
27
62
|
ids.add(value);
|
|
28
63
|
}
|
|
64
|
+
idsByAccount.set(accountId, ids);
|
|
29
65
|
}
|
|
30
66
|
} catch (err) {
|
|
31
67
|
console.error(`[watchdog] could not resolve ${safeChannel} allowFrom IDs: ${err.message}`);
|
|
32
68
|
}
|
|
69
|
+
return new Map(
|
|
70
|
+
Array.from(idsByAccount.entries()).map(([accountId, ids]) => [
|
|
71
|
+
accountId,
|
|
72
|
+
Array.from(ids),
|
|
73
|
+
]),
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const getPairedIds = ({
|
|
78
|
+
channel,
|
|
79
|
+
fsImpl = fs,
|
|
80
|
+
openclawDir = OPENCLAW_DIR,
|
|
81
|
+
}) => {
|
|
82
|
+
const ids = new Set();
|
|
83
|
+
const idsByAccount = getPairedTargetsByAccount({
|
|
84
|
+
channel,
|
|
85
|
+
fsImpl,
|
|
86
|
+
openclawDir,
|
|
87
|
+
});
|
|
88
|
+
for (const accountIds of idsByAccount.values()) {
|
|
89
|
+
for (const id of accountIds) {
|
|
90
|
+
ids.add(id);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
33
93
|
return Array.from(ids);
|
|
34
94
|
};
|
|
35
95
|
|
|
@@ -38,18 +98,30 @@ const formatDiscordMessage = (message) =>
|
|
|
38
98
|
|
|
39
99
|
/**
|
|
40
100
|
* Track thread state for Slack notifications
|
|
41
|
-
* Key: userId, Value: { threadTs, lastEvent }
|
|
101
|
+
* Key: accountId:userId, Value: { threadTs, lastEvent }
|
|
42
102
|
*/
|
|
43
103
|
const slackThreads = new Map();
|
|
44
104
|
|
|
45
|
-
const createWatchdogNotifier = ({
|
|
105
|
+
const createWatchdogNotifier = ({
|
|
106
|
+
telegramApi,
|
|
107
|
+
discordApi,
|
|
108
|
+
slackApi,
|
|
109
|
+
readEnvFile = () => [],
|
|
110
|
+
createSlackApi: createSlackApiFactory = createSlackApi,
|
|
111
|
+
fsImpl = fs,
|
|
112
|
+
openclawDir = OPENCLAW_DIR,
|
|
113
|
+
}) => {
|
|
46
114
|
const notify = async (message, opts = {}) => {
|
|
47
115
|
const summary = {
|
|
48
116
|
telegram: { sent: 0, failed: 0, skipped: false, targets: 0 },
|
|
49
117
|
discord: { sent: 0, failed: 0, skipped: false, targets: 0 },
|
|
50
118
|
slack: { sent: 0, failed: 0, skipped: false, targets: 0 },
|
|
51
119
|
};
|
|
52
|
-
const telegramTargets = getPairedIds(
|
|
120
|
+
const telegramTargets = getPairedIds({
|
|
121
|
+
channel: "telegram",
|
|
122
|
+
fsImpl,
|
|
123
|
+
openclawDir,
|
|
124
|
+
});
|
|
53
125
|
summary.telegram.targets = telegramTargets.length;
|
|
54
126
|
if (!telegramApi?.sendMessage || !process.env.TELEGRAM_BOT_TOKEN || telegramTargets.length === 0) {
|
|
55
127
|
summary.telegram.skipped = true;
|
|
@@ -67,7 +139,11 @@ const createWatchdogNotifier = ({ telegramApi, discordApi, slackApi }) => {
|
|
|
67
139
|
}
|
|
68
140
|
}
|
|
69
141
|
|
|
70
|
-
const discordTargets = getPairedIds(
|
|
142
|
+
const discordTargets = getPairedIds({
|
|
143
|
+
channel: "discord",
|
|
144
|
+
fsImpl,
|
|
145
|
+
openclawDir,
|
|
146
|
+
});
|
|
71
147
|
summary.discord.targets = discordTargets.length;
|
|
72
148
|
if (!discordApi?.sendDirectMessage || !process.env.DISCORD_BOT_TOKEN || discordTargets.length === 0) {
|
|
73
149
|
summary.discord.skipped = true;
|
|
@@ -85,61 +161,102 @@ const createWatchdogNotifier = ({ telegramApi, discordApi, slackApi }) => {
|
|
|
85
161
|
}
|
|
86
162
|
|
|
87
163
|
// Enhanced Slack notifications with threading and reactions
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
164
|
+
const slackTargetsByAccount = getPairedTargetsByAccount({
|
|
165
|
+
channel: "slack",
|
|
166
|
+
fsImpl,
|
|
167
|
+
openclawDir,
|
|
168
|
+
});
|
|
169
|
+
summary.slack.targets = Array.from(slackTargetsByAccount.values()).reduce(
|
|
170
|
+
(total, targets) => total + targets.length,
|
|
171
|
+
0,
|
|
172
|
+
);
|
|
173
|
+
if (summary.slack.targets === 0) {
|
|
91
174
|
summary.slack.skipped = true;
|
|
92
175
|
} else {
|
|
93
176
|
const eventType = opts.eventType || "info"; // crash, recovery, health, info
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
177
|
+
const envVars = typeof readEnvFile === "function" ? readEnvFile() : [];
|
|
178
|
+
|
|
179
|
+
const envMap = new Map(
|
|
180
|
+
(Array.isArray(envVars) ? envVars : [])
|
|
181
|
+
.map((entry) => [
|
|
182
|
+
String(entry?.key || "").trim(),
|
|
183
|
+
String(entry?.value || "").trim(),
|
|
184
|
+
])
|
|
185
|
+
.filter(([key]) => key),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
for (const [accountId, slackTargets] of slackTargetsByAccount.entries()) {
|
|
189
|
+
if (!slackTargets.length) continue;
|
|
190
|
+
const envKey = deriveSlackBotEnvKey(accountId);
|
|
191
|
+
const botToken = String(envMap.get(envKey) || process.env[envKey] || "").trim();
|
|
192
|
+
if (!botToken) {
|
|
193
|
+
summary.slack.failed += slackTargets.length;
|
|
194
|
+
for (const userId of slackTargets) {
|
|
195
|
+
console.error(
|
|
196
|
+
`[watchdog] slack notification failed for ${accountId}/${userId}: missing ${envKey}`,
|
|
197
|
+
);
|
|
106
198
|
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
107
201
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
202
|
+
const accountSlackApi =
|
|
203
|
+
accountId === "default" &&
|
|
204
|
+
slackApi?.postMessage &&
|
|
205
|
+
botToken === String(process.env.SLACK_BOT_TOKEN || "").trim()
|
|
206
|
+
? slackApi
|
|
207
|
+
: createSlackApiFactory(() => botToken);
|
|
208
|
+
|
|
209
|
+
for (const userId of slackTargets) {
|
|
210
|
+
try {
|
|
211
|
+
let threadTs = null;
|
|
212
|
+
let shouldCreateNewThread = true;
|
|
213
|
+
const threadKey = `${accountId}:${userId}`;
|
|
214
|
+
|
|
215
|
+
const existingThread = slackThreads.get(threadKey);
|
|
216
|
+
if (existingThread && existingThread.lastEvent === "crash" && eventType === "recovery") {
|
|
217
|
+
threadTs = existingThread.threadTs;
|
|
218
|
+
shouldCreateNewThread = false;
|
|
219
|
+
}
|
|
113
220
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
threadTs: result.ts,
|
|
118
|
-
lastEvent: eventType,
|
|
221
|
+
const result = await accountSlackApi.postMessage(userId, String(message || ""), {
|
|
222
|
+
thread_ts: threadTs,
|
|
223
|
+
mrkdwn: true,
|
|
119
224
|
});
|
|
120
|
-
}
|
|
121
225
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
226
|
+
if (shouldCreateNewThread && result.ts) {
|
|
227
|
+
slackThreads.set(threadKey, {
|
|
228
|
+
threadTs: result.ts,
|
|
229
|
+
lastEvent: eventType,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (result.ts && result.channel && accountSlackApi.addReaction) {
|
|
234
|
+
try {
|
|
235
|
+
if (eventType === "crash") {
|
|
236
|
+
await accountSlackApi.addReaction(result.channel, result.ts, "x");
|
|
237
|
+
} else if (eventType === "recovery") {
|
|
238
|
+
await accountSlackApi.addReaction(
|
|
239
|
+
result.channel,
|
|
240
|
+
result.ts,
|
|
241
|
+
"white_check_mark",
|
|
242
|
+
);
|
|
243
|
+
} else if (eventType === "health") {
|
|
244
|
+
await accountSlackApi.addReaction(result.channel, result.ts, "heart");
|
|
245
|
+
}
|
|
246
|
+
} catch (reactionErr) {
|
|
247
|
+
console.error(
|
|
248
|
+
`[watchdog] slack reaction failed for ${accountId}/${userId}: ${reactionErr.message}`,
|
|
249
|
+
);
|
|
132
250
|
}
|
|
133
|
-
} catch (reactionErr) {
|
|
134
|
-
// Reactions are nice-to-have, don't fail the whole notification
|
|
135
|
-
console.error(`[watchdog] slack reaction failed for ${userId}: ${reactionErr.message}`);
|
|
136
251
|
}
|
|
137
|
-
}
|
|
138
252
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
253
|
+
summary.slack.sent += 1;
|
|
254
|
+
} catch (err) {
|
|
255
|
+
summary.slack.failed += 1;
|
|
256
|
+
console.error(
|
|
257
|
+
`[watchdog] slack notification failed for ${accountId}/${userId}: ${err.message}`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
143
260
|
}
|
|
144
261
|
}
|
|
145
262
|
}
|
package/lib/server.js
CHANGED
|
@@ -138,6 +138,9 @@ const {
|
|
|
138
138
|
const {
|
|
139
139
|
ensureUsageTrackerPluginConfig,
|
|
140
140
|
} = require("./server/usage-tracker-config");
|
|
141
|
+
const {
|
|
142
|
+
ensureManagedExecDefaults,
|
|
143
|
+
} = require("./server/exec-defaults-config");
|
|
141
144
|
|
|
142
145
|
const { PORT, kTrustProxyHops, SETUP_API_PREFIXES } = constants;
|
|
143
146
|
|
|
@@ -223,7 +226,12 @@ const webhookMiddleware = createWebhookMiddleware({
|
|
|
223
226
|
const telegramApi = createTelegramApi(() => process.env.TELEGRAM_BOT_TOKEN);
|
|
224
227
|
const discordApi = createDiscordApi(() => process.env.DISCORD_BOT_TOKEN);
|
|
225
228
|
const slackApi = createSlackApi(() => process.env.SLACK_BOT_TOKEN);
|
|
226
|
-
const watchdogNotifier = createWatchdogNotifier({
|
|
229
|
+
const watchdogNotifier = createWatchdogNotifier({
|
|
230
|
+
telegramApi,
|
|
231
|
+
discordApi,
|
|
232
|
+
slackApi,
|
|
233
|
+
readEnvFile,
|
|
234
|
+
});
|
|
227
235
|
const watchdog = createWatchdog({
|
|
228
236
|
clawCmd,
|
|
229
237
|
launchGatewayProcess,
|
|
@@ -379,6 +387,11 @@ startServerLifecycle({
|
|
|
379
387
|
PORT,
|
|
380
388
|
isOnboarded,
|
|
381
389
|
runOnboardedBootSequence,
|
|
390
|
+
ensureManagedExecDefaults: () =>
|
|
391
|
+
ensureManagedExecDefaults({
|
|
392
|
+
fsModule: fs,
|
|
393
|
+
openclawDir: constants.OPENCLAW_DIR,
|
|
394
|
+
}),
|
|
382
395
|
ensureUsageTrackerPluginConfig: () =>
|
|
383
396
|
ensureUsageTrackerPluginConfig({
|
|
384
397
|
fsModule: fs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrysb/alphaclaw",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4-beta.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"express": "^4.21.0",
|
|
38
38
|
"http-proxy": "^1.18.1",
|
|
39
|
-
"openclaw": "
|
|
39
|
+
"openclaw": "2026.4.1",
|
|
40
40
|
"patch-package": "^8.0.1",
|
|
41
41
|
"ws": "^8.19.0"
|
|
42
42
|
},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
diff --git a/node_modules/openclaw/dist/gateway-cli-6Ksv5U_O.js b/node_modules/openclaw/dist/gateway-cli-6Ksv5U_O.js
|
|
2
|
+
index 4c742cee..bb87239b 100644
|
|
3
|
+
--- a/node_modules/openclaw/dist/gateway-cli-6Ksv5U_O.js
|
|
4
|
+
+++ b/node_modules/openclaw/dist/gateway-cli-6Ksv5U_O.js
|
|
5
|
+
@@ -26669,7 +26669,7 @@ function attachGatewayWsMessageHandler(params) {
|
|
6
|
+
close(1008, truncateCloseReason(authMessage));
|
|
7
|
+
};
|
|
8
|
+
const clearUnboundScopes = () => {
|
|
9
|
+
- if (scopes.length > 0) {
|
|
10
|
+
+ if (scopes.length > 0 && !sharedAuthOk) {
|
|
11
|
+
scopes = [];
|
|
12
|
+
connectParams.scopes = scopes;
|
|
13
|
+
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
### Prioritized Implementation Order
|
|
2
|
-
|
|
3
|
-
1. **[P0 | S] Unify toast type mapping in one place so callers consistently use semantic levels.**
|
|
4
|
-
- [x] Update `lib/public/js/components/toast.js` (`showToast`, `ToastContainer`) to normalize legacy values.
|
|
5
|
-
- [x] Normalize highest-impact callers first: `lib/public/js/components/providers.js`, `lib/public/js/components/models.js`, `lib/public/js/components/google.js`, `lib/public/js/components/gateway.js`, `lib/public/js/components/envars.js`, `lib/public/js/components/webhooks.js`.
|
|
6
|
-
- Current inconsistency snapshot:
|
|
7
|
-
- Legacy color callers: `lib/public/js/components/providers.js`, `lib/public/js/components/models.js`, `lib/public/js/components/google.js`.
|
|
8
|
-
- Semantic callers: `lib/public/js/components/envars.js`, `lib/public/js/components/webhooks.js`, `lib/public/js/components/gateway.js`, `lib/public/js/components/telegram-workspace.js`.
|
|
9
|
-
|
|
10
|
-
2. **[P0 | M] Add a shared restart banner component (and/or restart hook) for restart-required flows.**
|
|
11
|
-
- [x] Extract shared banner + handler state from: `lib/public/js/components/envars.js`, `lib/public/js/components/providers.js`, `lib/public/js/components/webhooks.js`.
|
|
12
|
-
- [x] Keep `restartGateway` integration aligned with existing gateway action UI in `lib/public/js/components/gateway.js`.
|
|
13
|
-
- Current inconsistency snapshot:
|
|
14
|
-
- Duplicate banner + state + handler logic in the same 3 files above.
|
|
15
|
-
|
|
16
|
-
3. **[P1 | S] Add a small shared loading spinner component to replace repeated inline SVG.**
|
|
17
|
-
- [x] Replace inline spinners in: `lib/public/js/app.js`, `lib/public/js/components/providers.js`, `lib/public/js/components/models.js`, `lib/public/js/components/onboarding/welcome-setup-step.js`.
|
|
18
|
-
- [x] Evaluate whether `lib/public/js/components/update-action-button.js` should consume the shared spinner as well.
|
|
19
|
-
- Current inconsistency snapshot:
|
|
20
|
-
- Spinner markup shape and sizing differ slightly by file.
|
|
21
|
-
|
|
22
|
-
4. **[P1 | M] Standardize button styles around `ac-btn-*` + common disabled treatment.**
|
|
23
|
-
- [x] Convert ad-hoc utility button patterns in: `lib/public/js/components/pairings.js` (Reject), `lib/public/js/components/device-pairings.js` (Reject), `lib/public/js/components/webhooks.js` (Create/Delete), `lib/public/js/components/google.js` (disconnect action), `lib/public/js/components/providers.js` (Codex reconnect/disconnect/restart).
|
|
24
|
-
- [x] Align onboarding variants in: `lib/public/js/components/onboarding/welcome-setup-step.js`, `lib/public/js/components/onboarding/welcome-form-step.js`, `lib/public/js/components/onboarding/welcome-pairing-step.js`.
|
|
25
|
-
- Current inconsistency snapshot:
|
|
26
|
-
- Mixed custom classes vs ad-hoc border utility buttons.
|
|
27
|
-
|
|
28
|
-
5. **[P1 | S] Normalize disabled opacity usage to one convention.**
|
|
29
|
-
- [x] Standardize `opacity-50` vs `opacity-60` in:
|
|
30
|
-
- `lib/public/js/components/envars.js`
|
|
31
|
-
- `lib/public/js/components/providers.js`
|
|
32
|
-
- `lib/public/js/components/webhooks.js`
|
|
33
|
-
- `lib/public/js/components/telegram-workspace.js`
|
|
34
|
-
- `lib/public/js/components/onboarding/welcome-setup-step.js`
|
|
35
|
-
- `lib/public/js/components/onboarding/welcome-pairing-step.js`
|
|
36
|
-
- Current inconsistency snapshot:
|
|
37
|
-
- `opacity-50` and `opacity-60` both used for disabled states.
|
|
38
|
-
|
|
39
|
-
6. **[P2 | M] Consider a shared action button wrapper for common save/create/delete states.**
|
|
40
|
-
- [x] Evaluate extraction points in: `lib/public/js/components/envars.js`, `lib/public/js/components/providers.js`, `lib/public/js/components/webhooks.js`, `lib/public/js/components/models.js`, `lib/public/js/components/pairings.js`, `lib/public/js/components/device-pairings.js`.
|
|
41
|
-
- Current inconsistency snapshot:
|
|
42
|
-
- Repeated action-label + loading-label + disabled-guard patterns.
|
|
43
|
-
|
|
44
|
-
7. **[P2 | M] Extract a shared modal shell wrapper for non-confirm custom modals.**
|
|
45
|
-
- [x] Factor shared overlay/panel shell from:
|
|
46
|
-
- `lib/public/js/components/webhooks.js` (`CreateWebhookModal`)
|
|
47
|
-
- `lib/public/js/components/credentials-modal.js`
|
|
48
|
-
- [x] Keep parity with `lib/public/js/components/confirm-dialog.js` behavior (overlay click + Escape semantics).
|
|
49
|
-
- Current inconsistency snapshot:
|
|
50
|
-
- Same modal shell classes/structure repeated with minor differences.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
diff --git a/node_modules/openclaw/dist/gateway-cli-DlnlX7IW.js b/node_modules/openclaw/dist/gateway-cli-DlnlX7IW.js
|
|
2
|
-
index ca48b932..c12478c4 100644
|
|
3
|
-
--- a/node_modules/openclaw/dist/gateway-cli-DlnlX7IW.js
|
|
4
|
-
+++ b/node_modules/openclaw/dist/gateway-cli-DlnlX7IW.js
|
|
5
|
-
@@ -25935,7 +25935,7 @@ function attachGatewayWsMessageHandler(params) {
|
|
6
|
-
close(1008, truncateCloseReason(authMessage));
|
|
7
|
-
};
|
|
8
|
-
const clearUnboundScopes = () => {
|
|
9
|
-
- if (scopes.length > 0) {
|
|
10
|
-
+ if (scopes.length > 0 && !sharedAuthOk) {
|
|
11
|
-
scopes = [];
|
|
12
|
-
connectParams.scopes = scopes;
|
|
13
|
-
}
|