@chrysb/alphaclaw 0.9.0-beta.7 → 0.9.1-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/bin/alphaclaw.js +26 -25
- package/lib/cli/git-runtime.js +97 -0
- package/lib/public/css/chat.css +0 -12
- package/lib/public/css/explorer.css +48 -0
- package/lib/public/css/shell.css +149 -0
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/css/theme.css +265 -0
- package/lib/public/dist/app.bundle.js +2770 -2762
- package/lib/public/js/app.js +26 -14
- package/lib/public/js/components/agents-tab/create-channel-modal.js +259 -59
- package/lib/public/js/components/gateway.js +0 -286
- package/lib/public/js/components/general/index.js +0 -7
- package/lib/public/js/components/icons.js +26 -25
- package/lib/public/js/components/modal-shell.js +1 -1
- package/lib/public/js/components/models-tab/provider-auth-card.js +60 -49
- package/lib/public/js/components/models-tab/use-models.js +74 -9
- package/lib/public/js/components/models.js +52 -37
- package/lib/public/js/components/onboarding/use-welcome-codex.js +34 -24
- package/lib/public/js/components/onboarding/welcome-config.js +76 -10
- package/lib/public/js/components/onboarding/welcome-form-step.js +2 -7
- package/lib/public/js/components/onboarding/welcome-header.js +12 -14
- package/lib/public/js/components/onboarding/welcome-setup-step.js +3 -3
- package/lib/public/js/components/providers.js +53 -42
- package/lib/public/js/components/routes/chat-route.js +2 -9
- package/lib/public/js/components/routes/general-route.js +0 -6
- package/lib/public/js/components/routes/index.js +0 -1
- package/lib/public/js/components/routes/watchdog-route.js +0 -6
- package/lib/public/js/components/sidebar.js +21 -7
- package/lib/public/js/components/theme-toggle.js +113 -0
- package/lib/public/js/components/update-modal.js +174 -51
- package/lib/public/js/components/watchdog-tab/index.js +0 -6
- package/lib/public/js/components/welcome/index.js +0 -2
- package/lib/public/js/components/welcome/use-welcome.js +107 -36
- package/lib/public/js/hooks/use-app-shell-controller.js +16 -33
- package/lib/public/js/lib/api.js +0 -28
- package/lib/public/js/lib/app-navigation.js +0 -2
- package/lib/public/js/lib/channel-provider-availability.js +1 -2
- package/lib/public/js/lib/codex-oauth-window.js +22 -0
- package/lib/public/js/lib/model-catalog.js +31 -0
- package/lib/public/js/lib/storage-keys.js +1 -1
- package/lib/public/login.html +8 -4
- package/lib/public/setup.html +9 -0
- package/lib/scripts/git +110 -16
- package/lib/server/agents/channels.js +1 -4
- package/lib/server/alphaclaw-version.js +590 -132
- package/lib/server/constants.js +5 -0
- package/lib/server/db/webhooks/index.js +48 -8
- package/lib/server/exec-defaults-config.js +163 -0
- package/lib/server/gateway.js +1 -0
- package/lib/server/init/register-server-routes.js +0 -8
- package/lib/server/init/server-lifecycle.js +2 -0
- package/lib/server/model-catalog-cache.js +251 -0
- package/lib/server/onboarding/github.js +83 -2
- package/lib/server/onboarding/index.js +7 -0
- package/lib/server/routes/models.js +14 -23
- package/lib/server/routes/nodes.js +9 -23
- package/lib/server/routes/system.js +3 -16
- package/lib/server/routes/webhooks.js +12 -1
- package/lib/server/startup.js +8 -0
- package/lib/server/watchdog-notify.js +172 -55
- package/lib/server.js +17 -2
- package/lib/setup/core-prompts/AGENTS.md +12 -0
- package/lib/setup/core-prompts/TOOLS.md +12 -0
- package/package.json +2 -2
- package/patches/openclaw+2026.4.9.patch +13 -0
- package/lib/public/js/components/mcp-tab/index.js +0 -237
- package/lib/public/js/components/routes/mcp-route.js +0 -7
- package/lib/server/mcp-bridge.js +0 -158
- package/lib/server/routes/mcp.js +0 -292
- package/patches/openclaw+2026.3.28.patch +0 -13
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { kFallbackOnboardingModels } = require("../constants");
|
|
2
|
+
const { createModelCatalogCache } = require("../model-catalog-cache");
|
|
2
3
|
|
|
3
4
|
const runModelsGitSync = async (shellCmd) => {
|
|
4
5
|
if (typeof shellCmd !== "function") return null;
|
|
@@ -22,6 +23,13 @@ const registerModelRoutes = ({
|
|
|
22
23
|
readEnvFile,
|
|
23
24
|
writeEnvFile,
|
|
24
25
|
reloadEnv,
|
|
26
|
+
modelCatalogCache = createModelCatalogCache({
|
|
27
|
+
shellCmd,
|
|
28
|
+
gatewayEnv,
|
|
29
|
+
parseJsonFromNoisyOutput,
|
|
30
|
+
normalizeOnboardingModels,
|
|
31
|
+
fallbackModels: kFallbackOnboardingModels,
|
|
32
|
+
}),
|
|
25
33
|
}) => {
|
|
26
34
|
const upsertEnvVar = (items, key, value) => {
|
|
27
35
|
const next = Array.isArray(items) ? [...items] : [];
|
|
@@ -154,29 +162,8 @@ const registerModelRoutes = ({
|
|
|
154
162
|
// ── Existing CLI-backed catalog/status routes ──
|
|
155
163
|
|
|
156
164
|
app.get("/api/models", async (req, res) => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
env: gatewayEnv(),
|
|
160
|
-
timeout: 20000,
|
|
161
|
-
});
|
|
162
|
-
const parsed = parseJsonFromNoisyOutput(output);
|
|
163
|
-
const models = normalizeOnboardingModels(parsed?.models || []);
|
|
164
|
-
if (models.length > 0) {
|
|
165
|
-
return res.json({ ok: true, source: "openclaw", models });
|
|
166
|
-
}
|
|
167
|
-
return res.json({
|
|
168
|
-
ok: true,
|
|
169
|
-
source: "fallback",
|
|
170
|
-
models: kFallbackOnboardingModels,
|
|
171
|
-
});
|
|
172
|
-
} catch (err) {
|
|
173
|
-
console.error("[models] Failed to load dynamic models:", err.message);
|
|
174
|
-
return res.json({
|
|
175
|
-
ok: true,
|
|
176
|
-
source: "fallback",
|
|
177
|
-
models: kFallbackOnboardingModels,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
165
|
+
const response = await modelCatalogCache.getCatalogResponse();
|
|
166
|
+
return res.json(response);
|
|
180
167
|
});
|
|
181
168
|
|
|
182
169
|
app.get("/api/models/status", async (req, res) => {
|
|
@@ -210,6 +197,7 @@ const registerModelRoutes = ({
|
|
|
210
197
|
env: gatewayEnv(),
|
|
211
198
|
timeout: 30000,
|
|
212
199
|
});
|
|
200
|
+
modelCatalogCache.markStale();
|
|
213
201
|
res.json({ ok: true });
|
|
214
202
|
} catch (err) {
|
|
215
203
|
res
|
|
@@ -286,6 +274,7 @@ const registerModelRoutes = ({
|
|
|
286
274
|
authProfiles.syncConfigAuthReferencesForAgent(agentId);
|
|
287
275
|
|
|
288
276
|
const syncWarning = await runModelsGitSync(shellCmd);
|
|
277
|
+
modelCatalogCache.markStale();
|
|
289
278
|
res.json({
|
|
290
279
|
ok: true,
|
|
291
280
|
...(syncWarning ? { syncWarning } : {}),
|
|
@@ -338,6 +327,7 @@ const registerModelRoutes = ({
|
|
|
338
327
|
const agentId = req.query.agentId || undefined;
|
|
339
328
|
authProfiles.upsertProfile(profileId, credential, agentId);
|
|
340
329
|
syncEnvVarsForProfiles([{ id: profileId, ...credential }]);
|
|
330
|
+
modelCatalogCache.markStale();
|
|
341
331
|
res.json({ ok: true });
|
|
342
332
|
} catch (err) {
|
|
343
333
|
res
|
|
@@ -359,6 +349,7 @@ const registerModelRoutes = ({
|
|
|
359
349
|
try {
|
|
360
350
|
const agentId = req.query.agentId || undefined;
|
|
361
351
|
const removed = authProfiles.removeProfile(profileId, agentId);
|
|
352
|
+
modelCatalogCache.markStale();
|
|
362
353
|
res.json({ ok: true, removed });
|
|
363
354
|
} catch (err) {
|
|
364
355
|
res
|
|
@@ -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
|
};
|
|
@@ -576,21 +576,6 @@ const registerSystemRoutes = ({
|
|
|
576
576
|
res.json({ ok: true, syncCron: status });
|
|
577
577
|
});
|
|
578
578
|
|
|
579
|
-
app.get("/api/openclaw/version", async (req, res) => {
|
|
580
|
-
const refresh = String(req.query.refresh || "") === "1";
|
|
581
|
-
const status = await openclawVersionService.getVersionStatus(refresh);
|
|
582
|
-
res.json(status);
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
app.post("/api/openclaw/update", async (req, res) => {
|
|
586
|
-
console.log("[alphaclaw] /api/openclaw/update requested");
|
|
587
|
-
const result = await openclawVersionService.updateOpenclaw();
|
|
588
|
-
console.log(
|
|
589
|
-
`[alphaclaw] /api/openclaw/update result: status=${result.status} ok=${result.body?.ok === true}`,
|
|
590
|
-
);
|
|
591
|
-
res.status(result.status).json(result.body);
|
|
592
|
-
});
|
|
593
|
-
|
|
594
579
|
app.get("/api/alphaclaw/version", async (req, res) => {
|
|
595
580
|
const refresh = String(req.query.refresh || "") === "1";
|
|
596
581
|
const status = await alphaclawVersionService.getVersionStatus(refresh);
|
|
@@ -636,7 +621,9 @@ const registerSystemRoutes = ({
|
|
|
636
621
|
);
|
|
637
622
|
if (result.status === 200 && result.body?.ok) {
|
|
638
623
|
res.json(result.body);
|
|
639
|
-
|
|
624
|
+
if (!result.body?.managedUpdate) {
|
|
625
|
+
setTimeout(() => alphaclawVersionService.restartProcess(), 1000);
|
|
626
|
+
}
|
|
640
627
|
} else {
|
|
641
628
|
res.status(result.status).json(result.body);
|
|
642
629
|
}
|
|
@@ -29,13 +29,24 @@ const mergeWebhookAndSummary = ({ webhook, summary }) => {
|
|
|
29
29
|
const totalCount = Number(summary?.totalCount || 0);
|
|
30
30
|
const errorCount = Number(summary?.errorCount || 0);
|
|
31
31
|
const successCount = Number(summary?.successCount || 0);
|
|
32
|
+
const recentTotalCount = Number(summary?.recentTotalCount || 0);
|
|
33
|
+
const recentErrorCount = Number(summary?.recentErrorCount || 0);
|
|
34
|
+
const recentSuccessCount = Number(summary?.recentSuccessCount || 0);
|
|
35
|
+
const healthWindowSize = Number(summary?.healthWindowSize || 0);
|
|
32
36
|
return {
|
|
33
37
|
...webhook,
|
|
34
38
|
lastReceived: summary?.lastReceived || null,
|
|
35
39
|
totalCount,
|
|
36
40
|
successCount,
|
|
37
41
|
errorCount,
|
|
38
|
-
|
|
42
|
+
recentTotalCount,
|
|
43
|
+
recentSuccessCount,
|
|
44
|
+
recentErrorCount,
|
|
45
|
+
healthWindowSize,
|
|
46
|
+
health: buildHealth({
|
|
47
|
+
totalCount: recentTotalCount || totalCount,
|
|
48
|
+
errorCount: recentTotalCount > 0 ? recentErrorCount : errorCount,
|
|
49
|
+
}),
|
|
39
50
|
};
|
|
40
51
|
};
|
|
41
52
|
|
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
|
|
|
@@ -193,7 +196,9 @@ const openclawVersionService = createOpenclawVersionService({
|
|
|
193
196
|
restartGateway,
|
|
194
197
|
isOnboarded,
|
|
195
198
|
});
|
|
196
|
-
const alphaclawVersionService = createAlphaclawVersionService(
|
|
199
|
+
const alphaclawVersionService = createAlphaclawVersionService({
|
|
200
|
+
readOpenclawVersion: () => openclawVersionService.readOpenclawVersion(),
|
|
201
|
+
});
|
|
197
202
|
const restartRequiredState = createRestartRequiredState({ isGatewayRunning });
|
|
198
203
|
const operationEvents = createOperationEventsService();
|
|
199
204
|
const chatWsService = createChatWsService({
|
|
@@ -223,7 +228,12 @@ const webhookMiddleware = createWebhookMiddleware({
|
|
|
223
228
|
const telegramApi = createTelegramApi(() => process.env.TELEGRAM_BOT_TOKEN);
|
|
224
229
|
const discordApi = createDiscordApi(() => process.env.DISCORD_BOT_TOKEN);
|
|
225
230
|
const slackApi = createSlackApi(() => process.env.SLACK_BOT_TOKEN);
|
|
226
|
-
const watchdogNotifier = createWatchdogNotifier({
|
|
231
|
+
const watchdogNotifier = createWatchdogNotifier({
|
|
232
|
+
telegramApi,
|
|
233
|
+
discordApi,
|
|
234
|
+
slackApi,
|
|
235
|
+
readEnvFile,
|
|
236
|
+
});
|
|
227
237
|
const watchdog = createWatchdog({
|
|
228
238
|
clawCmd,
|
|
229
239
|
launchGatewayProcess,
|
|
@@ -379,6 +389,11 @@ startServerLifecycle({
|
|
|
379
389
|
PORT,
|
|
380
390
|
isOnboarded,
|
|
381
391
|
runOnboardedBootSequence,
|
|
392
|
+
ensureManagedExecDefaults: () =>
|
|
393
|
+
ensureManagedExecDefaults({
|
|
394
|
+
fsModule: fs,
|
|
395
|
+
openclawDir: constants.OPENCLAW_DIR,
|
|
396
|
+
}),
|
|
382
397
|
ensureUsageTrackerPluginConfig: () =>
|
|
383
398
|
ensureUsageTrackerPluginConfig({
|
|
384
399
|
fsModule: fs,
|
|
@@ -25,6 +25,18 @@ If any of these apply, outline your approach first — what you intend to do, in
|
|
|
25
25
|
|
|
26
26
|
Your `.openclaw` directory is version-controlled and this is how work survives container restarts.
|
|
27
27
|
|
|
28
|
+
### Persistent Storage Rules
|
|
29
|
+
|
|
30
|
+
This deployment runs in an ephemeral container. `/tmp`, other temp directories, and files outside `/data` can disappear on restart or redeploy.
|
|
31
|
+
|
|
32
|
+
Anything that must survive redeploys must live under `/data/.openclaw`.
|
|
33
|
+
|
|
34
|
+
For plugins and other durable artifacts:
|
|
35
|
+
|
|
36
|
+
- Prefer normal `openclaw plugins install <spec>` flows for persistent installs.
|
|
37
|
+
- If you must stage or unpack a local plugin first, stage it under `/data/.openclaw/...`, not `/tmp/...`.
|
|
38
|
+
- Never persist `plugins.load.paths` entries that point at temp directories.
|
|
39
|
+
|
|
28
40
|
Anytime you add, edit, or remove workspace files, openclaw.json, cron.json, skills, or external resources (third-party pages, databases, integrations), **commit and push your changes to git**. Never force push; always pull first if there might be remote changes.
|
|
29
41
|
|
|
30
42
|
Whenever you do this, end your message with a **Changes committed** summary. Use workspace-relative paths for local files.
|
|
@@ -21,6 +21,18 @@ Do not deflect actionable requests to the Setup UI. If a command or tool is avai
|
|
|
21
21
|
|
|
22
22
|
Changes to env vars are made through the **Envars** tab (`{{SETUP_UI_URL}}#envars`). After saving, a gateway restart may be required to pick up the changes — the UI prompts for this automatically. Do not edit `/data/.env` directly; use the Setup UI so changes are validated and the gateway restart is handled.
|
|
23
23
|
|
|
24
|
+
### Persistent storage
|
|
25
|
+
|
|
26
|
+
This deployment runs in an ephemeral container. `/tmp` and other temp locations do not survive redeploys.
|
|
27
|
+
|
|
28
|
+
Anything persistent must live under `/data/.openclaw`.
|
|
29
|
+
|
|
30
|
+
For plugins and local tooling:
|
|
31
|
+
|
|
32
|
+
- Prefer normal `openclaw plugins install <spec>` flows for durable installs.
|
|
33
|
+
- If you need to stage a local plugin or helper files first, put them under `/data/.openclaw/...`, not `/tmp/...`.
|
|
34
|
+
- Do not leave durable `plugins.load.paths` entries pointing at temp directories.
|
|
35
|
+
|
|
24
36
|
### Google Workspace
|
|
25
37
|
|
|
26
38
|
Google Workspace is connected via the **General** tab (`{{SETUP_UI_URL}}#general`). The user provides OAuth client credentials from Google Cloud Console, then authorizes access to the services they need (Gmail, Calendar, Drive, Sheets, Docs, Tasks, Contacts, Meet). Connected accounts and `gog` CLI usage are covered by the gog-cli skill.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrysb/alphaclaw",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1-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": "2026.
|
|
39
|
+
"openclaw": "2026.4.10",
|
|
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/server.impl-BxLfE9ri.js b/node_modules/openclaw/dist/server.impl-BxLfE9ri.js
|
|
2
|
+
index e97a5374..4cd08799 100644
|
|
3
|
+
--- a/node_modules/openclaw/dist/server.impl-BxLfE9ri.js
|
|
4
|
+
+++ b/node_modules/openclaw/dist/server.impl-BxLfE9ri.js
|
|
5
|
+
@@ -28254,7 +28254,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
|
+
}
|